/** * 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.AbstractByteWriter; import io.horizondb.io.files.FileDataOutput; 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; import static org.apache.commons.lang.Validate.notNull; /** * <code>FileDataOutput</code> that split the file in block of specified size. * <p> * The blocks can be either a data block or an header block. Each block is prefixed by one byte specifying the type of * the block. * </p> * * @author Benjamin */ final class BlockOrganizedFileDataOutput extends AbstractByteWriter implements FileDataOutput { /** * The size of the blocks. */ private final int blockSize; /** * The decorated output. */ private final FileDataOutput output; /** * The prefix of the next block. */ private byte nextBlockPrefix = DATA_BLOCK_PREFIX; /** * The position of the next block. */ private long nextBlockPosition; /** * Creates a new <code>BlockOrganizedFileDataOutput</code> which use the specified block size and write to the * specified output. * * @param blockSize the block size. * @param output the underlying output. * @throws IOException if an I/O problem occurs. */ public BlockOrganizedFileDataOutput(int blockSize, FileDataOutput output) throws IOException { notNull(output, "the output parameter must not be null."); isTrue(blockSize > 1, "the blockSize parameter must be greater than one."); this.blockSize = blockSize; this.output = output; initNextBlockPosition(); fillBlock(); } /** * Initializes the position of the next block. * * @throws IOException if a problem occurs while retrieving the file size. */ private void initNextBlockPosition() throws IOException { long numberOfBlocks = (long) Math.ceil(((double) this.output.getPosition()) / this.blockSize); this.nextBlockPosition = numberOfBlocks * this.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 boolean isDataBlock() { return this.nextBlockPrefix == 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 boolean isHeaderBlock() { return this.nextBlockPrefix == HEADER_BLOCK_PREFIX; } /** * Switches from the current type of block to the other type. * <p> * For example if the current block is a DATA block the remaining bytes of the block will be filled with zero and * the block type will become HEADER. * </p> * * @throws IOException if an IO problem occurs during the switch. */ public void switchBlockType() throws IOException { fillBlock(); if (isDataBlock()) { this.nextBlockPrefix = HEADER_BLOCK_PREFIX; } else { this.nextBlockPrefix = DATA_BLOCK_PREFIX; } } /** * {@inheritDoc} */ @Override public BlockOrganizedFileDataOutput writeByte(int b) throws IOException { if (isNewBlock()) { writeBlockPrefix(); computeNextBlockPosition(); } this.output.writeByte(b); return this; } /** * {@inheritDoc} */ @Override public long getPosition() throws IOException { long realPosition = this.output.getPosition(); long numberOfBlocks = (long) Math.ceil(((double) realPosition) / this.blockSize); return realPosition - numberOfBlocks; } /** * {@inheritDoc} */ @Override public BlockOrganizedFileDataOutput writeBytes(byte[] bytes, int offset, int length) throws IOException { int writeableBytes = writeableBytes(); if (writeableBytes == 0) { writeBlockPrefix(); computeNextBlockPosition(); writeableBytes = this.blockSize - 1; } if (writeableBytes >= length) { this.output.writeBytes(bytes, offset, length); } else { this.output.writeBytes(bytes, offset, writeableBytes); writeBytes(bytes, offset + writeableBytes, length - writeableBytes); } return this; } /** * {@inheritDoc} */ @Override public void flush() throws IOException { this.output.flush(); } /** * {@inheritDoc} */ @Override public void close() throws IOException { this.output.close(); } /** * {@inheritDoc} */ @Override public BlockOrganizedFileDataOutput writeZeroBytes(int length) throws IOException { int writeableBytes = writeableBytes(); if (writeableBytes == 0) { writeBlockPrefix(); computeNextBlockPosition(); writeableBytes = this.blockSize - 1; } if (writeableBytes >= length) { this.output.writeZeroBytes(length); } else { this.output.writeZeroBytes(writeableBytes); writeZeroBytes(length - writeableBytes); } return this; } /** * Fills the remaining bytes of the current block with zeros. * * @throws IOException if an IO problem occurs. */ private void fillBlock() throws IOException { int writeableBytes = writeableBytes(); if (writeableBytes != 0) { this.output.writeZeroBytes(writeableBytes); } } /** * Returns the number of bytes free within the current block. * * @return the number of bytes free within the current block * @throws IOException if an IO problem occurs */ private int writeableBytes() throws IOException { return (int) (this.nextBlockPosition - this.output.getPosition()); } /** * Computes the position of the next block. */ private void computeNextBlockPosition() { this.nextBlockPosition += this.blockSize; } /** * Returns <code>true</code> if we are in a new block, <code>false</code> otherwise. * * @return <code>true</code> if we are in a new block, <code>false</code> otherwise. * @throws IOException if an IO problem occurs. */ private boolean isNewBlock() throws IOException { return this.output.getPosition() == this.nextBlockPosition; } /** * Writes the prefix for this block. * * @throws IOException if an IO problem occurs. */ private void writeBlockPrefix() throws IOException { this.output.writeByte(this.nextBlockPrefix); } }