/**
* 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.files.RandomAccessDataFile;
import io.horizondb.io.files.SeekableFileDataOutput;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import com.codahale.metrics.MetricRegistry;
/**
* <code>NodeManager</code> implementation that store its data on disk.
*/
public final class OnDiskNodeManager<K extends Comparable<K>, V> implements NodeManager<K, V> {
/**
* The name of this component.
*/
private final String name;
/**
* The file path.
*/
private final RandomAccessDataFile file;
/**
* The writer used to write to the file.
*/
private final NodeWriter<K, V> writer;
/**
* The reader used to read the file.
*/
private final NodeReader<K, V> reader;
/**
* The cache used to reduce disk read.
*/
private final NodeCache<K, V> cache;
/**
* The current root node.
*/
private Node<K, V> root;
/**
* Creates a new <code>OnDiskNodeManager</code> instance.
*
* @param name the name of this component
* @param path the file path
* @param writerFactory the writer factory
* @param readerFactory the reader factory
* @throws IOException if an I/O problem occurs while opening the file.
*/
public OnDiskNodeManager(String name,
Path path,
NodeWriterFactory<K, V> writerFactory,
NodeReaderFactory<K, V> readerFactory)
throws IOException {
this(name, path, writerFactory, readerFactory, new NodeCache<K, V>("NoopCache", 0));
}
/**
* Creates a new <code>OnDiskNodeManager</code> instance.
*
* @param name the name of this component
* @param path the file path
* @param writerFactory the writer factory
* @param readerFactory the reader factory
* @param cache the node cache
* @throws IOException if an I/O problem occurs while opening the file.
*/
public OnDiskNodeManager(String name,
Path path,
NodeWriterFactory<K, V> writerFactory,
NodeReaderFactory<K, V> readerFactory,
NodeCache<K, V> cache) throws IOException {
this.name = name;
this.file = RandomAccessDataFile.open(path, true);
SeekableFileDataOutput output = this.file.getOutput();
output.seek(this.file.size());
this.writer = writerFactory.newWriter(output);
this.reader = readerFactory.newReader(this.file.newInput());
this.cache = cache;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
/**
* {@inheritDoc}
*/
@Override
public void register(MetricRegistry registry) {
}
/**
* {@inheritDoc}
*/
@Override
public void unregister(MetricRegistry registry) {
}
/**
* {@inheritDoc}
*/
@Override
public Node<K, V> getRoot(BTree<K, V> btree) throws IOException {
if (this.root == null) {
this.root = this.reader.readRoot(btree);
}
return this.root;
}
/**
* {@inheritDoc}
*/
@Override
public void setRoot(Node<K, V> root) throws IOException {
this.root = root;
this.writer.writeRoot(this.root);
this.writer.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
this.writer.closeQuietly();
this.reader.closeQuietly();
this.file.closeQuietly();
}
/**
* {@inheritDoc}
*/
@Override
public Node<K, V> wrapNode(Node<K, V> node) throws IOException {
if (node instanceof NodeProxy) {
return node;
}
long position = this.writer.getPosition();
int subTreeSize = this.writer.writeNode(node);
NodeProxy<K, V> proxy = new NodeProxy<K, V>(node.getBTree(), position, subTreeSize);
this.cache.put(proxy, node);
return proxy;
}
/**
* {@inheritDoc}
*/
@Override
public Node<K, V> unwrapNode(Node<K, V> node) throws IOException {
if (node instanceof NodeProxy) {
return ((NodeProxy<K, V>) node).loadNode();
}
return node;
}
@Override
@SafeVarargs
public final Node<K, V>[] wrapNodes(Node<K, V>... nodes) throws IOException {
@SuppressWarnings("unchecked")
Node<K, V>[] newNodes = new Node[nodes.length];
for (int i = 0; i < nodes.length; i++) {
newNodes[i] = wrapNode(nodes[i]);
}
return newNodes;
}
/**
* {@inheritDoc}
*/
@Override
public ValueWrapper<V> wrapValue(V value) throws IOException {
long position = this.writer.getPosition();
this.writer.writeData(value);
int length = (int) (this.writer.getPosition() - position);
return new ValueProxy<>(this, position, length);
}
/**
* Loads the data corresponding to the specified proxy.
*
* @param proxy the data proxy.
* @return the data associated to the specified proxy.
* @throws IOException if the data cannot be read.
*/
public V loadValue(DataPointer<K, V> proxy) throws IOException {
return this.reader.readData(proxy.getPosition());
}
/**
* Loads the node corresponding to the specified proxy.
*
* @param proxy the node proxy.
* @return the node associated to the specified proxy.
* @throws IOException if the node cannot be read.
*/
public Node<K, V> loadNode(final NodeProxy<K, V> proxy) throws IOException {
return this.cache.get(proxy, this.reader);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("file", this.file).toString();
}
}