/** * 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.ByteReader; import io.horizondb.io.ReadableBuffer; import io.horizondb.io.checksum.ChecksumByteReader; import io.horizondb.io.files.SeekableFileDataInput; import java.io.IOException; import java.util.SortedMap; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static io.horizondb.io.encoding.VarInts.readUnsignedInt; import static io.horizondb.io.encoding.VarInts.readUnsignedLong; /** * @author Benjamin * */ public abstract class AbstractNodeReader<K extends Comparable<K>, V> implements NodeReader<K, V> { /** * The instance logger. */ private final Logger logger = LoggerFactory.getLogger(getClass()); /** * The file input. */ private final BlockOrganizedFileDataInput input; /** * The reader decorator used to validate checksums. */ private final ChecksumByteReader checksumReader; /** * The version of the file format. */ private int version = Constants.CURRENT_VERSION; /** * The compression that has been used to compress the data. */ private int compression = Constants.NO_COMPRESSION; public AbstractNodeReader(SeekableFileDataInput input) throws IOException { this.input = new BlockOrganizedFileDataInput(Constants.BLOCK_SIZE, input); this.checksumReader = ChecksumByteReader.wrap(this.input); } /** * {@inheritDoc} */ @Override public final Node<K, V> readRoot(BTree<K, V> btree) throws IOException { if (!this.input.seekHeader()) { this.logger.debug("No header found within the file creating root node."); return new LeafNode<>(btree); } int length = readUnsignedInt(this.input); this.checksumReader.resetChecksum(); ByteReader nodeReader = this.checksumReader.slice(length); this.version = nodeReader.readByte(); this.compression = nodeReader.readByte(); return readNode(btree, nodeReader); } /** * {@inheritDoc} */ @Override public V readData(long position) throws IOException { this.input.seek(position); int length = readUnsignedInt(this.input); this.checksumReader.resetChecksum(); ReadableBuffer slice = this.checksumReader.slice(length); if (!this.checksumReader.readChecksum()) { throw new IllegalStateException("A CRC mismatch has occured while reading data at position: " + position + " of length: " + length); } return readValue(slice); } /** * @param slice * @return * @throws IOException */ protected abstract V readValue(ByteReader reader) throws IOException; /** * {@inheritDoc} */ @Override public void close() throws IOException { this.input.close(); } /** * {@inheritDoc} */ @Override public void closeQuietly() { try { close(); } catch (Exception e) { // Do nothing. } } /** * {@inheritDoc} */ @Override public Node<K, V> readNode(BTree<K, V> btree, long position) throws IOException { this.input.seek(position); int length = readUnsignedInt(this.input); this.checksumReader.resetChecksum(); ReadableBuffer slice = this.checksumReader.slice(length); if (!this.checksumReader.readChecksum()) { throw new IllegalStateException("A CRC mismatch has occured while reading node data at position: " + position + " of length: " + length); } return readNode(btree, slice); } /** * @param btree * @param nodeReader * @return * @throws IOException */ private Node<K, V> readNode(BTree<K, V> btree, ByteReader reader) throws IOException { int type = reader.readByte(); int subTreeSize = readUnsignedInt(reader); if (isLeafNode(type)) { SortedMap<K, ValueWrapper<V>> records = readNodeRecords(btree, reader); return LeafNode.<K, V> newInstance(btree, records); } SortedMap<K, Node<K, V>> children = readNodeChildren(btree, reader); return InternalNode.<K, V> newInstance(btree, children); } /** * @param btree * @param reader * @return * @throws IOException */ private SortedMap<K, ValueWrapper<V>> readNodeRecords(BTree<K, V> btree, ByteReader reader) throws IOException { SortedMap<K, ValueWrapper<V>> records = new TreeMap<K, ValueWrapper<V>>(); while (reader.isReadable()) { records.put(readKey(reader), readValueWrapper(btree, reader)); } return records; } private SortedMap<K, Node<K, V>> readNodeChildren(BTree<K, V> btree, ByteReader reader) throws IOException { SortedMap<K, Node<K, V>> children = new TreeMap<K, Node<K, V>>(); while (reader.isReadable()) { children.put(readKey(reader), readNodeProxy(btree, reader)); } return children; } protected ValueWrapper<V> readValueWrapper(BTree<K, V> btree, ByteReader reader) throws IOException { long position = readUnsignedLong(reader); int lenght = readUnsignedInt(reader); OnDiskNodeManager<K, V> manager = (OnDiskNodeManager<K, V>) btree.getManager(); return new ValueProxy<K, V>(manager, position, lenght); } protected Node<K, V> readNodeProxy(BTree<K, V> btree, ByteReader reader) throws IOException { long position = readUnsignedLong(reader); int subTreeSize = readUnsignedInt(reader); return new NodeProxy<>(btree, position, subTreeSize); } /** * @return * @throws IOException */ protected abstract K readKey(ByteReader reader) throws IOException; /** * @param type * @return */ private static boolean isLeafNode(int type) { return Node.LEAF_NODE == type; } }