package com.revolsys.io.page;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import com.revolsys.collection.map.LruMap;
import com.revolsys.io.FileUtil;
public class FileMappedPageManager implements PageManager {
private FileChannel fileChannel;
private final Set<Integer> freePageIndexes = new TreeSet<>();
// TODO
private final Map<Integer, Page> pages = new LruMap<>(1000);
private final Set<Page> pagesInUse = new HashSet<>();
int pageSize = 2048;
private RandomAccessFile randomAccessFile;
public FileMappedPageManager() {
this(FileUtil.newTempFile("pages", ".pf"));
}
public FileMappedPageManager(final File file) {
try {
this.randomAccessFile = new RandomAccessFile(file, "rw");
this.fileChannel = this.randomAccessFile.getChannel();
} catch (final FileNotFoundException e) {
throw new IllegalArgumentException("Unable to open file " + file.getAbsolutePath(), e);
}
}
@Override
public int getNumPages() {
return this.pages.size();
}
@Override
public synchronized Page getPage(final int index) {
synchronized (this.pages) {
if (this.freePageIndexes.contains(index)) {
throw new IllegalArgumentException("Page does not exist " + index);
} else {
Page page = this.pages.get(index);
if (page == null) {
page = loadPage(index);
}
if (this.pagesInUse.contains(page)) {
throw new IllegalArgumentException("Page is currently being used " + index);
} else {
this.pagesInUse.add(page);
page.setOffset(0);
return page;
}
}
}
}
@Override
public int getPageSize() {
return this.pageSize;
}
private Page loadPage(final int index) {
try {
final MappedByteBuffer buffer = this.fileChannel.map(MapMode.READ_WRITE,
(long)index * this.pageSize, this.pageSize);
final Page page = new FileMappedPage(this, index, buffer);
this.pages.put(index, page);
return page;
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized Page newPage() {
synchronized (this.pages) {
Page page;
if (this.freePageIndexes.isEmpty()) {
try {
final int index = (int)(this.randomAccessFile.length() / this.pageSize);
final long offset = (long)index * this.pageSize;
this.randomAccessFile.setLength(offset + this.pageSize);
final FileChannel channel = this.randomAccessFile.getChannel();
final MappedByteBuffer buffer = channel.map(MapMode.READ_WRITE, offset, this.pageSize);
page = new FileMappedPage(this, index, buffer);
this.pages.put(page.getIndex(), page);
} catch (final IOException e) {
throw new RuntimeException(e);
}
} else {
final Iterator<Integer> iterator = this.freePageIndexes.iterator();
final Integer pageIndex = iterator.next();
iterator.remove();
page = loadPage(pageIndex);
}
this.pagesInUse.add(page);
return page;
}
}
@Override
public Page newTempPage() {
return new ByteArrayPage(this, -1, this.pageSize);
}
@Override
public synchronized void releasePage(final Page page) {
write(page);
this.pagesInUse.remove(page);
}
@Override
public synchronized void removePage(final Page page) {
synchronized (this.pages) {
page.clear();
write(page);
this.freePageIndexes.add(page.getIndex());
}
}
@Override
public synchronized void write(final Page page) {
if (page.getPageManager() == this) {
if (page instanceof FileMappedPage) {
page.flush();
}
}
}
}