package com.codecademy.eventhub.list;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.codecademy.eventhub.base.ByteBufferUtil;
import com.codecademy.eventhub.base.Schema;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* numRecordsPerFile * schema.getObjectSize() can't exceed MappedByteBuffer size limit, i.e.
* numRecordsPerFile < (2^31 - 1) / schema.getObjectSize()
*/
public class DmaList<T> implements Closeable {
private final String directory;
private final Schema<T> schema;
private final MappedByteBuffer metaDataBuffer;
// O(numFiles)
private LoadingCache<Integer, MappedByteBuffer> buffers;
private long maxId;
private int numRecordsPerFile;
public DmaList(String directory, Schema<T> schema, MappedByteBuffer metaDataBuffer,
LoadingCache<Integer, MappedByteBuffer> buffers, long maxId, int numRecordsPerFile) {
this.directory = directory;
this.schema = schema;
this.metaDataBuffer = metaDataBuffer;
this.buffers = buffers;
this.maxId = maxId;
this.numRecordsPerFile = numRecordsPerFile;
}
public void add(T t) {
int currentBufferIndex = (int) (maxId / numRecordsPerFile);
ByteBuffer duplicate = buffers.getUnchecked(currentBufferIndex).duplicate();
duplicate.position((int) (maxId % numRecordsPerFile) * schema.getObjectSize());
duplicate.put(schema.toBytes(t));
metaDataBuffer.putLong(0, ++maxId);
}
public void update(long id, T t) {
if (id > maxId) {
maxId = id;
}
int currentBufferIndex = (int) (id / numRecordsPerFile);
ByteBuffer duplicate = buffers.getUnchecked(currentBufferIndex).duplicate();
duplicate.position((int) (id % numRecordsPerFile) * schema.getObjectSize());
duplicate.put(schema.toBytes(t));
}
public T get(long kthRecord) {
int objectSize = schema.getObjectSize();
byte[] bytes = new byte[objectSize];
ByteBuffer newBuffer = buffers.getUnchecked((int) (kthRecord / numRecordsPerFile)).duplicate();
newBuffer.position((int) (kthRecord % numRecordsPerFile) * objectSize);
newBuffer.get(bytes, 0, objectSize);
return schema.fromBytes(bytes);
}
public byte[] getBytes(long kthRecord) {
int objectSize = schema.getObjectSize();
byte[] bytes = new byte[objectSize];
ByteBuffer newBuffer = buffers.getUnchecked((int) (kthRecord / numRecordsPerFile)).duplicate();
newBuffer.position((int) (kthRecord % numRecordsPerFile) * objectSize);
newBuffer.get(bytes, 0, objectSize);
return bytes;
}
public long getMaxId() {
return maxId;
}
public String getVarz(int indentation) {
String indent = new String(new char[indentation]).replace('\0', ' ');
return String.format(
indent + "directory: %s\n" +
indent + "buffer: %s",
directory, buffers.stats().toString());
}
@Override
public void close() {
buffers.invalidateAll();
}
public static <T> DmaList<T> build(final Schema<T> schema, final String directory,
final int numRecordsPerFile, int cacheSize) {
//noinspection ResultOfMethodCallIgnored
new File(directory).mkdirs();
try (RandomAccessFile raf = new RandomAccessFile(new File(
String.format("%s/meta_data.mem", directory)), "rw")) {
MappedByteBuffer metaDataBuffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 8);
long numRecords = metaDataBuffer.getLong();
final int fileSize = numRecordsPerFile * schema.getObjectSize();
LoadingCache<Integer, MappedByteBuffer> buffers = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.recordStats()
.removalListener(new RemovalListener<Integer, MappedByteBuffer>() {
@Override
public void onRemoval(RemovalNotification<Integer, MappedByteBuffer> notification) {
MappedByteBuffer value = notification.getValue();
if (value != null) {
value.force();
}
}})
.build(new CacheLoader<Integer, MappedByteBuffer>() {
@Override
public MappedByteBuffer load(Integer key) throws Exception {
return ByteBufferUtil.createNewBuffer(
String.format("%s/dma_list_%d.mem", directory, key), fileSize);
}
});
return new DmaList<>(directory, schema, metaDataBuffer, buffers, numRecords, numRecordsPerFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}