/** * Copyright 2013 Benjamin Lerer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.horizondb.db.btree; import io.horizondb.io.AbstractByteReader; import io.horizondb.io.ByteReader; import io.horizondb.io.ReadableBuffer; import java.io.IOException; import static io.horizondb.db.btree.BlockPrefixes.DATA_BLOCK_PREFIX; import static io.horizondb.db.btree.BlockPrefixes.HEADER_BLOCK_PREFIX; import static org.apache.commons.lang.Validate.isTrue; /** * Base class for the block organized <code>ByteReader</code>. * * @author Benjamin * */ abstract class AbstractBlockOrganizedByteReader extends AbstractByteReader { /** * The size of the blocks. */ private final int blockSize; /** * The prefix of the next block. */ private byte currentBlockPrefix; /** * The position of the next block. */ private long nextBlockPosition; /** * The recyclable slice. */ private BlockOrganizedReadableBuffer slice; /** * @param input * @throws IOException */ public AbstractBlockOrganizedByteReader(int blockSize) { isTrue(blockSize > 0, "the blockSize parameter must be greater than zero."); this.blockSize = blockSize; } /** * Returns <code>true</code> if the current block is a data block. * * @return <code>true</code> if the current block is a data block. */ public final boolean isDataBlock() { return this.currentBlockPrefix == DATA_BLOCK_PREFIX; } /** * Returns <code>true</code> if the current block is an header block. * * @return <code>true</code> if the current block is an header block. */ public final boolean isHeaderBlock() { return this.currentBlockPrefix == HEADER_BLOCK_PREFIX; } /** * {@inheritDoc} */ @Override public final ByteReader skipBytes(int numberOfBytes) throws IOException { isTrue(numberOfBytes >= 0, "Number of bytes must be greater or equals to zero"); for (int i = 0; i < numberOfBytes; i++) { this.readByte(); } return this; } /** * {@inheritDoc} */ @Override public final ReadableBuffer slice(int length) throws IOException { if (this.slice == null) { this.slice = new BlockOrganizedReadableBuffer(this.blockSize); } long blockPosition = this.nextBlockPosition - getRealPosition(); byte blockPrefix = this.currentBlockPrefix; this.slice.decorate(getRealSlice(length), blockPosition, blockPrefix, length); return this.slice; } /** * {@inheritDoc} */ @Override public final byte readByte() throws IOException { if (isNewBlock()) { handleNewBlock(); } return doReadByte(); } /** * {@inheritDoc} */ @Override public final ByteReader readBytes(byte[] bytes) throws IOException { return readBytes(bytes, 0, bytes.length); } /** * {@inheritDoc} */ @Override public final ByteReader readBytes(byte[] bytes, int offset, int length) throws IOException { int readableBytes = readableBytes(); if (readableBytes == 0) { handleNewBlock(); readableBytes = this.blockSize - 1; } if (readableBytes >= length) { doReadBytes(bytes, offset, length); } else { doReadBytes(bytes, offset, readableBytes); readBytes(bytes, offset + readableBytes, length - readableBytes); } return this; } /** * {@inheritDoc} */ @Override public final boolean isReadable() throws IOException { long remaining = getRealSize() - getRealPosition(); if (remaining > 1) { return true; } return (remaining == 1) && (getRealPosition() != this.nextBlockPosition); } /** * Returns the real position within the file. * * @return the real position within the file. * @throws IOException if an I/O problem occurs. */ protected abstract long getRealPosition() throws IOException; /** * Reads the next byte available from the underlying file. * * @return the next byte available from the underlying file. * @throws IOException if an I/O problem occurs. */ protected abstract byte doReadByte() throws IOException; /** * Read the specified amount of bytes from the underlying file. * * @param bytes the array that must be filled with the next bytes available. * @param offset the position where the bytes must be written in the array. * @param length the number of bytes that must be transfered. * @throws IOException if an I/O problem occurs while reading the data. */ protected abstract void doReadBytes(byte[] bytes, int offset, int length) throws IOException; /** * Returns the size of the underlying file. * * @return the size of the underlying file. */ protected abstract long getRealSize() throws IOException; /** * Returns the slice from the specified length. * * @param length the slice length. * @return the slice from the specified length. */ protected abstract ReadableBuffer doSlice(int length) throws IOException; /** * Handle the change of block. * * @throws IOException if an I/O problem occurs. */ protected final void handleNewBlock() throws IOException { this.nextBlockPosition = getRealPosition() + this.blockSize; this.currentBlockPrefix = doReadByte(); } /** * Returns the block size. * * @return the block size. */ protected final int getBlockSize() { return this.blockSize; } /** * Sets the position of the next block. * * @param position the position of the next block. */ protected final void setNextBlockPosition(long position) { this.nextBlockPosition = position; } /** * Sets the prefix of the current block. * * @param currentBlockPrefix the prefix of the current block. */ protected final void setCurrentBlockPrefix(byte currentBlockPrefix) { this.currentBlockPrefix = currentBlockPrefix; } /** * Returns the amount of readable bytes within this block. * * @return the amount of readable bytes within this block. * @throws IOException if an I/O problem occurs. */ private int readableBytes() throws IOException { return (int) (this.nextBlockPosition - getRealPosition()); } /** * Returns <code>true</code> if the current position is the start of a new block. * * @return <code>true</code> if the current position is the start of a new block. * @throws IOException if an I/O problem occurs. */ private boolean isNewBlock() throws IOException { return getRealPosition() == this.nextBlockPosition; } /** * Return the slice from the underlying <code>ByteReader</code> that contains the specified amount of data bytes. * * @return the slice from the underlying <code>ByteReader</code> that contains the specified amount of data bytes. * @throws IOException if an I/O problem occurs. */ private ReadableBuffer getRealSlice(int length) throws IOException { int realLength = length; long position = getRealPosition(); long remaining = length; while (position + remaining > this.nextBlockPosition) { realLength++; remaining -= this.nextBlockPosition - position; position = this.nextBlockPosition; this.nextBlockPosition += this.blockSize; } ReadableBuffer buffer = doSlice(realLength); if (realLength > length) { this.currentBlockPrefix = buffer.getByte((int) (length - remaining)); } return buffer; } }