/*
* Copyright 2011 Peter Lawrey
*
* 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 vanilla.java.chronicle.impl;
import sun.nio.ch.DirectBuffer;
import vanilla.java.chronicle.Excerpt;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
/**
* The fastest and most extensible Chronicle.
*
* @author peter.lawrey
*/
public class IndexedChronicle extends AbstractChronicle {
public static final long MAX_VIRTUAL_ADDRESS = 1L << 48;
private final List<MappedByteBuffer> indexBuffers = new ArrayList<MappedByteBuffer>();
private final List<MappedByteBuffer> dataBuffers = new ArrayList<MappedByteBuffer>();
private final int indexBitSize;
protected final int indexLowMask;
private final int dataBitSize;
private final int dataLowMask;
private final FileChannel indexChannel;
private final FileChannel dataChannel;
private boolean useUnsafe = false;
private final ByteOrder byteOrder;
public IndexedChronicle(String basePath, int dataBitSizeHint) throws IOException {
this(basePath, dataBitSizeHint, ByteOrder.nativeOrder());
}
public IndexedChronicle(String basePath, int dataBitSizeHint, ByteOrder byteOrder) throws IOException {
super(extractName(basePath));
this.byteOrder = byteOrder;
indexBitSize = Math.min(30, Math.max(12, dataBitSizeHint - 4));
dataBitSize = Math.min(30, Math.max(12, dataBitSizeHint));
indexLowMask = (1 << indexBitSize) - 1;
dataLowMask = (1 << dataBitSize) - 1;
File parentFile = new File(basePath).getParentFile();
if (parentFile != null)
parentFile.mkdirs();
indexChannel = new RandomAccessFile(basePath + ".index", "rw").getChannel();
dataChannel = new RandomAccessFile(basePath + ".data", "rw").getChannel();
// find the last record.
long indexSize = indexChannel.size() >>> indexBitSize();
if (indexSize > 0) {
while (--indexSize > 0 && getIndexData(indexSize) == 0) ;
//System.out.println(basePath + ", size=" + indexSize);
size = indexSize;
} else {
//System.out.println(basePath + " created.");
}
}
private static String extractName(String basePath) {
File file = new File(basePath);
String name = file.getName();
if (name != null && name.length() > 0)
return name;
file = file.getParentFile();
if (file == null) return "chronicle";
name = file.getName();
if (name != null && name.length() > 0)
return name;
return "chronicle";
}
protected int indexBitSize() {
return 3;
}
public void useUnsafe(boolean useUnsafe) {
this.useUnsafe = useUnsafe && byteOrder == ByteOrder.nativeOrder();
}
public boolean useUnsafe() {
return useUnsafe;
}
public ByteOrder byteOrder() {
return byteOrder;
}
@Override
public Excerpt<IndexedChronicle> createExcerpt() {
return useUnsafe ? new UnsafeExcerpt<IndexedChronicle>(this) : new ByteBufferExcerpt<IndexedChronicle>(this);
}
public UnsafeExcerpt<IndexedChronicle> createUnsafeExcerpt() {
if(!useUnsafe) throw new IllegalStateException("Cannot acquire an unsafe excerpt for a non-unsafe chronicle", new Throwable());
return new UnsafeExcerpt<IndexedChronicle>(this);
}
@Override
public long getIndexData(long indexId) {
long indexOffset = indexId << indexBitSize();
ByteBuffer indexBuffer = acquireIndexBuffer(indexOffset);
return indexBuffer.getLong((int) (indexOffset & indexLowMask));
}
protected ByteBuffer acquireIndexBuffer(long startPosition) {
if (startPosition >= MAX_VIRTUAL_ADDRESS)
throw new IllegalStateException("ByteOrder is incorrect.");
int indexBufferId = (int) (startPosition >> indexBitSize);
while (indexBuffers.size() <= indexBufferId) indexBuffers.add(null);
ByteBuffer buffer = indexBuffers.get(indexBufferId);
if (buffer != null)
return buffer;
try {
// long start = System.nanoTime();
MappedByteBuffer mbb = indexChannel.map(FileChannel.MapMode.READ_WRITE, startPosition & ~indexLowMask, 1 << indexBitSize);
// long time = System.nanoTime() - start;
// System.out.println(Thread.currentThread().getName()+": map "+time);
mbb.order(byteOrder);
indexBuffers.set(indexBufferId, mbb);
return mbb;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public ByteBuffer acquireDataBuffer(long startPosition) {
if (startPosition >= MAX_VIRTUAL_ADDRESS)
throw new IllegalStateException("ByteOrder is incorrect.");
int dataBufferId = (int) (startPosition >> dataBitSize);
while (dataBuffers.size() <= dataBufferId) dataBuffers.add(null);
ByteBuffer buffer = dataBuffers.get(dataBufferId);
if (buffer != null)
return buffer;
try {
MappedByteBuffer mbb = dataChannel.map(FileChannel.MapMode.READ_WRITE, startPosition & ~dataLowMask, 1 << dataBitSize);
mbb.order(ByteOrder.nativeOrder());
dataBuffers.set(dataBufferId, mbb);
return mbb;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public int positionInBuffer(long startPosition) {
return (int) (startPosition & dataLowMask);
}
@Override
public void setIndexData(long indexId, long indexData) {
long indexOffset = indexId << indexBitSize();
ByteBuffer indexBuffer = acquireIndexBuffer(indexOffset);
indexBuffer.putLong((int) (indexOffset & indexLowMask), indexData);
}
@Override
public long startExcerpt(int capacity) {
long startPosition = getIndexData(size);
assert size == 0 || startPosition != 0;
// does it overlap a ByteBuffer barrier.
if ((startPosition & ~dataLowMask) != ((startPosition + capacity) & ~dataLowMask)) {
// resize the previous entry.
startPosition = (startPosition + dataLowMask) & ~dataLowMask;
setIndexData(size, startPosition);
}
return startPosition;
}
@Override
public void incrSize() {
size++;
}
/**
* Clear any previous data in the Chronicle.
* <p/>
* Added for testing purposes.
*/
public void clear() {
size = 0;
setIndexData(1, 0);
}
public void close() {
try {
clearAll(indexChannel, indexBuffers);
} finally {
clearAll(dataChannel, dataBuffers);
}
}
private void clearAll(FileChannel channel, List<MappedByteBuffer> buffers) {
try {
for (MappedByteBuffer buffer : buffers) {
if (buffer != null) {
buffer.force();
}
}
} finally {
try {
channel.close();
} catch (IOException ignored) {
}
for (MappedByteBuffer buffer : buffers) {
if (buffer instanceof DirectBuffer)
((DirectBuffer) buffer).cleaner().clean();
}
}
buffers.clear();
}
}