package io.eguan.nrs;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import java.lang.ref.PhantomReference;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.util.Objects;
import javax.annotation.Nonnull;
/**
* Class representing the H1 header at the start of the NRS file.
* <p>
* <b>Note</b>: this class does not lock anything. The caller must ensure that read and write are consistent.
* <p>
* The header contains:
* <ul>
* <li>the file version
* <li>the L1 table
* </ul>
*
* @author oodrive
* @author llambert
* @author pwehrle
*
*/
final class H1Header {
/** Index of the version in the longTableView */
private static final int VERSION_INDEX = 0;
/** Offset to add to get the address of a L2 table */
private static final int L1TABLE_OFFSET = 1;
/**
* Wraps the given {@link MappedByteBuffer} into a new instance.
* <ul>
* <li>In line with its purpose, this does not make any defensive copy.</li>
* <li>Byte order is determined by the provided buffer when calling this method, and must not be changed afterwards.
* </li>
* <li>The capacity of the table is computed from the limit of the provided buffer, so the state of the resulting
* instance rests on it.</li>
* <li>Neither cluster nor word alignment of the buffer are verified.</li>
* </ul>
*
* @param mappedBuffer
* a non-{@code null} {@link MappedByteBuffer} instance of sufficient capacity
* @return a functional instance of {@link H1Header}
*/
final static H1Header wrap(@Nonnull final MappedByteBuffer mappedBuffer) {
return new H1Header(Objects.requireNonNull(mappedBuffer));
}
/**
* Internal buffer pointing to the memory-mapped file region.
*/
private MappedByteBuffer memoryMappedTable;
/** Keep phantom reference to ensure GC */
private final PhantomReference<MappedByteBuffer> ref;
/**
* View on the {@link #memoryMappedTable} as a {@link LinkBuffer}.
*/
private LongBuffer longTableView;
/** Version of the file */
private long version;
/**
* Constructs a new instance backed by the provided {@link MappedByteBuffer}.
*
* @param mappedTable
* the table mapped in memory to use as backend
*/
private H1Header(final MappedByteBuffer mappedTable) {
super();
this.memoryMappedTable = mappedTable;
this.ref = new PhantomReference<>(mappedTable, null);
this.longTableView = this.memoryMappedTable.asLongBuffer();
// Read the version
this.version = longTableView.get(VERSION_INDEX);
}
/**
* Get the current value of the {@link NrsFile} version.
*
* @return the version of the {@link NrsFile}.
*/
final long getVersion() {
return version;
}
/**
* Increment the version and save it in the file.
*/
final void incrVersion() {
longTableView.put(VERSION_INDEX, ++version);
}
/**
* Reload the version from the file.
*/
final void loadVersion() {
this.version = longTableView.get(VERSION_INDEX);
}
/**
* Reads a {@link Long#SIZE long-sized} L2 address from the given offset.
*
* @param l1Offset
* the offset in the L1 table
* @return the L2 address read at the requested offset, or zero if
*/
final long readL2Address(final int l1Offset) {
return longTableView.get(L1TABLE_OFFSET + l1Offset);
}
/**
* Writes the given L2 address to an entry of the table.
*
* @param l1Offset
* the entry offset to write
* @param l2Address
* the address to write
*/
final void writeL2Address(final int l1Offset, final long l2Address) {
this.longTableView.put(L1TABLE_OFFSET + l1Offset, l2Address);
}
/**
* Prepare a {@link ByteBuffer} that allows iteration over the L2tables addresses from an image of the h1header.
*
*/
final void prepareIterator(final ByteBuffer h1headerContents) {
h1headerContents.order(longTableView.order()).rewind();
h1headerContents.position(L1TABLE_OFFSET * NrsFileHeader.BYTES_PER_LONG);
}
final void close() {
// TODO: need to force sync to ensure the writing of the buffer? This call costs a lot...
// memoryMappedTable.force();
// Unreference mapped buffer
memoryMappedTable = null;
longTableView = null;
ref.get(); // Make compiler happy
}
}