TmpFileDataStore.java
/*
* (C) Copyright 2015-2017 by MSDK Development Team
*
* This software is dual-licensed under either
*
* (a) the terms of the GNU Lesser General Public License version 2.1 as published by the Free
* Software Foundation
*
* or (per the licensee's choosing)
*
* (b) the terms of the Eclipse Public License v1.0 as published by the Eclipse Foundation.
*/
package io.github.msdk.datamodel.datastore;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.msdk.MSDKRuntimeException;
/**
* A DataPointStore implementation that stores the data points in a temporary file. This is a simple
* and efficient method, but has one disadvantage - removing the data points is an expensive
* operation, so this implementation actually only removes the reference but the data still remain
* in the temporary file. If a single instance is continuously used to add and remove data points,
* the file will grow indefinitely.
*
* Since this class stores data on disk, there is a risk that IOException may occur. If that
* happens, the IOException is wrapped in a MSDKRuntimeException and thrown.
*
* The methods of this class are synchronized, therefore it can be safely used by multiple threads.
*/
class TmpFileDataStore implements DataPointStore {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final File tmpDataFileName;
private final RandomAccessFile tmpDataFile;
// Start with a ~20 KB byte buffer, that will be expanded based on needs
private ByteBuffer byteBuffer = ByteBuffer.allocate(20000);
private final HashMap<Integer, Long> dataPointsOffsets = new HashMap<>();
private final HashMap<Integer, Integer> dataPointsLengths = new HashMap<>();
private int lastStorageId = 0;
TmpFileDataStore() {
try {
tmpDataFileName = File.createTempFile("msdk", ".tmp");
logger.debug("Initializing a new tmp-file data store in " + tmpDataFileName);
tmpDataFile = new RandomAccessFile(tmpDataFileName, "rw");
/*
* Lock the temporary file.
*/
FileChannel fileChannel = tmpDataFile.getChannel();
fileChannel.lock();
tmpDataFileName.deleteOnExit();
} catch (IOException e) {
throw new MSDKRuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
@Nonnull
synchronized public Object storeData(@Nonnull Object array, @Nonnull Integer size) {
if (byteBuffer == null)
throw new IllegalStateException("This object has been disposed");
try {
final long currentOffset = tmpDataFile.length();
int objectSize;
if (array.getClass().getComponentType().equals(Double.TYPE))
objectSize = Double.SIZE / 8;
else if (array.getClass().getComponentType().equals(Float.TYPE))
objectSize = Float.SIZE / 8;
else
throw new IllegalArgumentException("Unsupported array type");
// Calculate minimum necessary size of the byte buffer
final int numOfBytes = size * objectSize;
// Make sure we have enough space in the byte buffer
if (byteBuffer.capacity() < numOfBytes) {
byteBuffer = ByteBuffer.allocate(numOfBytes * 2);
} else {
byteBuffer.clear();
}
if (array.getClass().getComponentType().equals(Double.TYPE))
convertArrayToByteBuffer((double[]) array, size);
else if (array.getClass().getComponentType().equals(Float.TYPE))
convertArrayToByteBuffer((float[]) array, size);
tmpDataFile.write(byteBuffer.array(), 0, numOfBytes);
// Increase the storage ID
lastStorageId++;
// Save the reference to the new items
dataPointsOffsets.put(lastStorageId, currentOffset);
dataPointsLengths.put(lastStorageId, size);
} catch (IOException e) {
throw new MSDKRuntimeException(e);
}
return lastStorageId;
}
/** {@inheritDoc} */
@Override
synchronized public void loadData(@Nonnull Object id, @Nonnull Object array) {
if (byteBuffer == null)
throw new IllegalStateException("This object has been disposed");
if (!dataPointsLengths.containsKey(id))
throw new IllegalArgumentException(
"ID " + id + " not found in storage file " + tmpDataFileName);
// Get file offset and number of data points
final long offset = dataPointsOffsets.get(id);
final int numOfDataPoints = dataPointsLengths.get(id);
if (!array.getClass().isArray())
throw new IllegalArgumentException("The provided argument is not an array");
if (Array.getLength(array) < numOfDataPoints)
throw new IllegalArgumentException("The provided array does not fit all loaded objects");
int objectSize;
if (array.getClass().getComponentType().equals(Double.TYPE))
objectSize = Double.SIZE / 8;
else if (array.getClass().getComponentType().equals(Float.TYPE))
objectSize = Float.SIZE / 8;
else
throw new IllegalArgumentException("Unsupported array type");
// Calculate minimum necessary size of the byte buffer
final int numOfBytes = numOfDataPoints * objectSize;
// Make sure we have enough space in the byte buffer
if (byteBuffer.capacity() < numOfBytes) {
byteBuffer = ByteBuffer.allocate(numOfBytes * 2);
} else {
byteBuffer.clear();
}
try {
// Read values
tmpDataFile.seek(offset);
tmpDataFile.read(byteBuffer.array(), 0, numOfBytes);
if (array.getClass().getComponentType().equals(Double.TYPE))
convertByteBufferToArray((double[]) array, numOfDataPoints);
else if (array.getClass().getComponentType().equals(Float.TYPE))
convertByteBufferToArray((float[]) array, numOfDataPoints);
} catch (IOException e) {
throw new MSDKRuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
synchronized public void removeData(@Nonnull Object id) {
if (byteBuffer == null)
throw new IllegalStateException("This object has been disposed");
dataPointsOffsets.remove(id);
dataPointsLengths.remove(id);
}
/** {@inheritDoc} */
@Override
synchronized public void dispose() {
// Discard the hash maps and byte buffer
dataPointsOffsets.clear();
dataPointsLengths.clear();
byteBuffer = null;
// Remove the temporary file
if (tmpDataFileName.exists()) {
logger.debug("Removing tmp-file " + tmpDataFileName);
try {
tmpDataFile.close();
tmpDataFileName.delete();
} catch (IOException e) {
logger.warn(
"Could not close and remove temporary file " + tmpDataFileName + ": " + e.toString());
e.printStackTrace();
}
}
}
/**
* {@inheritDoc}
*
* When this object is garbage collected, remove the associated temporary data file from disk.
*/
@Override
protected void finalize() {
dispose();
}
private void convertArrayToByteBuffer(float[] data, int size) {
FloatBuffer fltBuffer = byteBuffer.asFloatBuffer();
for (int i = 0; i < size; i++)
fltBuffer.put(data[i]);
}
private void convertArrayToByteBuffer(double[] data, int size) {
DoubleBuffer dblBuffer = byteBuffer.asDoubleBuffer();
for (int i = 0; i < size; i++)
dblBuffer.put(data[i]);
}
private void convertByteBufferToArray(float[] array, Integer size) {
FloatBuffer fltBuffer = byteBuffer.asFloatBuffer();
for (int i = 0; i < size; i++) {
array[i] = fltBuffer.get();
}
}
private void convertByteBufferToArray(double[] array, Integer size) {
DoubleBuffer dblBuffer = byteBuffer.asDoubleBuffer();
for (int i = 0; i < size; i++) {
array[i] = dblBuffer.get();
}
}
}