package it.cavallium.strangedb.database; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantReadWriteLock; public class DatabaseFileIO implements IFileIO { private final AsynchronousFileChannel dataFileChannel; private final AtomicLong firstFreeIndex; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false); private volatile boolean closed = false; public DatabaseFileIO(Path dataFile) throws IOException { dataFileChannel = AsynchronousFileChannel.open(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE); firstFreeIndex = new AtomicLong(dataFileChannel.size()); } @Override public ByteBuffer readAt(long index, int length) throws IOException { lock.readLock().lock(); try { if (closed) throw new IOException("Database closed!"); ByteBuffer dataBuffer = ByteBuffer.allocate(length); try { dataFileChannel.read(dataBuffer, index).get(); } catch (InterruptedException e) { throw new IOException(e); } catch (ExecutionException e) { throw new IOException(e.getCause()); } dataBuffer.flip(); return dataBuffer; } finally { lock.readLock().unlock(); } } @Override public int writeAt(long index, int length, ByteBuffer data) throws IOException { lock.writeLock().lock(); try { return writeAt_(index, length, data); } finally { lock.writeLock().unlock(); } } private int writeAt_(long index, int length, ByteBuffer data) throws IOException { if (closed) throw new IOException("Database closed!"); if (data.position() != 0) { throw new IOException("You didn't flip the ByteBuffer!"); } firstFreeIndex.updateAndGet((firstFreeIndex) -> firstFreeIndex < index + length ? index + length : firstFreeIndex); try { return dataFileChannel.write(data, index).get(); } catch (InterruptedException e) { throw new IOException(e); } catch (ExecutionException e) { throw new IOException(e.getCause()); } } @Override public long writeAtEnd(int length, ByteBuffer data) throws IOException { lock.writeLock().lock(); try { if (closed) throw new IOException("Database closed!"); long index = firstFreeIndex.getAndAdd(length); writeAt_(index, length, data); return index; } finally { lock.writeLock().unlock(); } } @Override public void close() throws IOException { lock.writeLock().lock(); try { if (closed) throw new IOException("Database already closed!"); closed = true; dataFileChannel.close(); } finally { lock.writeLock().unlock(); } } }