/** * 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.ByteWriter; import io.horizondb.io.checksum.ChecksumByteWriter; import io.horizondb.io.files.FileDataOutput; import java.io.IOException; import static io.horizondb.io.encoding.VarInts.computeUnsignedIntSize; import static io.horizondb.io.encoding.VarInts.computeUnsignedLongSize; import static io.horizondb.io.encoding.VarInts.writeByte; import static io.horizondb.io.encoding.VarInts.writeUnsignedInt; import static io.horizondb.io.encoding.VarInts.writeUnsignedLong; import static org.apache.commons.lang.Validate.notNull; /** * Base class for writing B+Tree nodes to the disk. * * @author Benjamin */ public abstract class AbstractNodeWriter<K extends Comparable<K>, V> implements NodeWriter<K, V> { /** * The length in byte needed to write the file format version. */ private static final int FILE_FORMAT_VERSION_LENGTH = 1; /** * The compression type. */ private static final int COMPRESSION_TYPE_LENGTH = 1; /** * The length in byte needed to write the node type. */ private static final int NODE_TYPE_LENGTH = 1; /** * The file output. */ private final BlockOrganizedFileDataOutput output; /** * The writer decorator used to compute checksums. */ private final ChecksumByteWriter checksumWriter; /** * Creates a new writer that will write to the specified output. * * @param output the output towards the file. * @throws IOException if a problem occurs while writing to the disk. */ public AbstractNodeWriter(FileDataOutput output) throws IOException { notNull(output, "the output parameter must not be null."); this.output = new BlockOrganizedFileDataOutput(Constants.BLOCK_SIZE, output); this.checksumWriter = ChecksumByteWriter.wrap(this.output); } /** * {@inheritDoc} */ @Override public void closeQuietly() { try { close(); } catch (Exception e) { // Do nothing. } } /** * {@inheritDoc} */ @Override public final void close() throws IOException { this.output.close(); } /** * {@inheritDoc} */ @Override public final void flush() throws IOException { this.output.flush(); } /** * {@inheritDoc} */ @Override public final int writeNode(Node<K, V> node) throws IOException { NodeSizeCalculator calculator = new NodeSizeCalculator(); node.accept(calculator); int nodeSize = calculator.getNodeSize(); int subTreeSize = calculator.getSubTreeSize(); int length = NODE_TYPE_LENGTH + computeUnsignedIntSize(subTreeSize) + nodeSize; writeUnsignedInt(this.output, length); writeNode(node, subTreeSize); return subTreeSize; } /** * @param node * @param subTreeSize * @throws IOException */ private void writeNode(Node<K, V> node, int subTreeSize) throws IOException { writeNodeType(node); writeUnsignedInt(this.checksumWriter, subTreeSize); NodePersister persister = new NodePersister(this.checksumWriter); node.accept(persister); this.checksumWriter.writeChecksum(); this.checksumWriter.reset(); } /** * {@inheritDoc} */ @Override public final int writeData(V value) throws IOException { long position = getPosition(); writeUnsignedInt(this.output, computeValueSize(value)); writeValue(this.checksumWriter, value); this.checksumWriter.writeChecksum(); this.checksumWriter.reset(); return (int) (getPosition() - position); } /** * {@inheritDoc} */ @Override public final long getPosition() throws IOException { return this.output.getPosition(); } /** * {@inheritDoc} */ @Override public final void writeRoot(Node<K, V> node) throws IOException { this.output.switchBlockType(); NodeSizeCalculator calculator = new NodeSizeCalculator(); node.accept(calculator); int nodeSize = calculator.getNodeSize(); int subTreeSize = calculator.getSubTreeSize(); int length = FILE_FORMAT_VERSION_LENGTH + COMPRESSION_TYPE_LENGTH + NODE_TYPE_LENGTH + computeUnsignedIntSize(subTreeSize) + nodeSize; writeUnsignedInt(this.output, length); writeVersion(); writeCompressionType(); writeNode(node, subTreeSize); this.output.switchBlockType(); } /** * Computes the size in bytes needed to store the specified key. * * @param key the key. * @return the size in bytes needed to store the specified key. * @throws IOException if an I/O problem occurs while computing the key size */ protected abstract int computeKeySize(K key) throws IOException; /** * Computes the size in bytes needed to store the specified value. * * @param value the value * @return the size in bytes needed to store the specified value. * @throws IOException if an I/O problem occurs while computing the value size */ protected abstract int computeValueSize(V value) throws IOException; /** * Writes the specified key to the disk. * * @param writer the writer that must be used to write the data. * @param key the key to write. * @throws IOException if an I/O problem occurs while writing the data to the disk. */ protected abstract void writeKey(ByteWriter writer, K key) throws IOException; /** * Writes the specified value to the disk. * * @param writer the writer that must be used to write the data. * @param value the value to write. * @throws IOException if an I/O problem occurs while writing the data to the disk. */ protected abstract void writeValue(ByteWriter writer, V value) throws IOException; /** * Writes the type of node. * * @param node the node for which the type must be written. * @throws IOException if an I/O problem occurs while writing. */ private void writeNodeType(Node<K, V> node) throws IOException { writeByte(this.checksumWriter, node.getType()); } /** * Writes the type of compression used within the file. * * @throws IOException if an I/O problem occurs while writing. */ private void writeCompressionType() throws IOException { writeByte(this.checksumWriter, Constants.NO_COMPRESSION); } /** * Writes the version of the file format. * * @throws IOException if an I/O problem occurs while writing. */ private void writeVersion() throws IOException { writeByte(this.checksumWriter, Constants.CURRENT_VERSION); } /** * <code>NodeVisitor</code> that compute the size in bytes required to store the visited node. */ private final class NodeSizeCalculator extends SimpleNodeVisitor<K, V> { /** * The size in bytes of the node. */ private int nodeSize; /** * The size in bytes used by the sub-tree. */ private int subTreeSize; /** * Returns the size in bytes of the node. * * @return the size in bytes of the node. */ public int getNodeSize() { return this.nodeSize; } /** * Returns the size in bytes used by the sub-tree. * * @return the size in bytes used by the sub-tree. */ public int getSubTreeSize() { return this.subTreeSize; } /** * {@inheritDoc} */ @Override public NodeVisitResult preVisitNode(K key, Node<K, V> node) throws IOException { NodeProxy<K, V> proxy = (NodeProxy<K, V>) node; this.nodeSize += computeKeySize(key) + computeUnsignedLongSize(proxy.getPosition()) + computeUnsignedIntSize(proxy.getSubTreeSize()); this.subTreeSize += proxy.getSubTreeSize(); return NodeVisitResult.SKIP_SUBTREE; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public NodeVisitResult visitRecord(K key, ValueWrapper<V> wrapper) throws IOException { DataPointer<K, V> pointer = (DataPointer<K, V>) wrapper; this.nodeSize += computeKeySize(key) + computeUnsignedLongSize(pointer.getPosition()) + computeUnsignedIntSize(pointer.getSubTreeSize()); this.subTreeSize += pointer.getSubTreeSize(); return NodeVisitResult.CONTINUE; } } /** * <code>NodeVisitor</code> that serialize to the disk the node that it is visiting. */ private final class NodePersister extends SimpleNodeVisitor<K, V> { /** * The writer used to write the data to the disk. */ private final ByteWriter writer; /** * Creates a new <code>NodePersister</code> that use the specified writer to write data to the disk. * * @param writer the writer. */ public NodePersister(ByteWriter writer) { this.writer = writer; } /** * {@inheritDoc} */ @Override public NodeVisitResult preVisitNode(K key, Node<K, V> node) throws IOException { NodeProxy<K, V> proxy = (NodeProxy<K, V>) node; writeKey(this.writer, key); writeUnsignedLong(this.writer, proxy.getPosition()); writeUnsignedInt(this.writer, proxy.getSubTreeSize()); return NodeVisitResult.SKIP_SUBTREE; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public NodeVisitResult visitRecord(K key, ValueWrapper<V> wrapper) throws IOException { DataPointer<K, V> pointer = (DataPointer<K, V>) wrapper; writeKey(this.writer, key); writeUnsignedLong(this.writer, pointer.getPosition()); writeUnsignedInt(this.writer, pointer.getSubTreeSize()); return NodeVisitResult.CONTINUE; } } }