package net.sourceforge.pain.db;
import net.sourceforge.pain.util.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
/**
* User: fmike Date: 04.03.2003 Time: 19:43:28
*/
final class DbPageMapper {
private static final byte[] HEADER = new byte[]{0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5};
private static final int NUMBER_OF_NEW_DB_PAGES = 1024;
private static final long FIRST_PAGE_OFFSET = 64;
private static final int DEFAULT_PAGE_SIZE_BITS = 6;
/** numbers of free pages*/
private final DbIntBuffer freePageNums;
/** dirty pages nums, page images for this pageNums are in pageIndex*/
private final DbIntBuffer dirtyPages = new DbIntBuffer(4096);
/** page images for dirty pages*/
private Object[] pageIndex;
/** pool of pages ( byte arrays), all dirty images return into this pool after flush*/
private DbObjBuffer pageImagePool;
final byte[] ZERO_PAGE;
private final RandomAccessFile file;
int pageSize;
int pageSizeBits;
private float extentionQuantum = 1.3F;
private final String fileName;
private ByteBuffer STARTUP_FILE_BUFF;
final byte[] STARTUP_TMP_PAGE;
DbPageMapper(final String fileName) throws IOException {
this.fileName = fileName;
file = new RandomAccessFile(fileName, "rw");
readMapperMetaInfo();
freePageNums = new DbIntBuffer((int) (file.length() / pageSize));
ZERO_PAGE = new byte[pageSize];
STARTUP_TMP_PAGE = new byte[pageSize];
// startup optimization, all file cached
cacheFile();
final int capacity = 1024;
pageImagePool = new DbObjBuffer(capacity);
for (int i = capacity; --i >= 0;) {
pageImagePool.add(new byte[pageSize]);
}
}
private void cacheFile() throws IOException {
STARTUP_FILE_BUFF = ByteBuffer.allocateDirect((int) (file.length() - FIRST_PAGE_OFFSET));
final FileChannel channel = file.getChannel();
channel.read(STARTUP_FILE_BUFF, FIRST_PAGE_OFFSET);
}
private void readMapperMetaInfo() throws IOException {
final int numberOfPages;
if (file.length() != 0) {
final byte[] fileHeader = new byte[HEADER.length];
file.read(fileHeader);
if (!Arrays.equals(HEADER, fileHeader)) {
throw new IOException("INVALID FILE FORMAT");
}
pageSizeBits = file.read();
if (pageSizeBits == 0) {
throw new RuntimeException("Invalid page size:" + pageSize);
}
pageSize = 1 << pageSizeBits;
numberOfPages = (int) ((file.length() - FIRST_PAGE_OFFSET) / pageSize);
} else {
Log.debug("creating new file:" + fileName);
numberOfPages = NUMBER_OF_NEW_DB_PAGES;
file.setLength(FIRST_PAGE_OFFSET + NUMBER_OF_NEW_DB_PAGES << DEFAULT_PAGE_SIZE_BITS);
file.seek(0);
file.write(HEADER);
file.write(DEFAULT_PAGE_SIZE_BITS);
pageSizeBits = DEFAULT_PAGE_SIZE_BITS;
pageSize = 1 << pageSizeBits;
file.seek(FIRST_PAGE_OFFSET);
final byte[] zeroPage = new byte[pageSize];
for (int i = 0; i < numberOfPages; i++) {
file.write(zeroPage);
}
}
pageIndex = new Object[numberOfPages];
Log.debug("Page size :" + pageSize + " number of pages:" + numberOfPages);
}
void startup_markPageAsUsed(final int pageNo) {
pageIndex[pageNo] = ZERO_PAGE;
}
void startup_markPageAsUsed(final int[] pageNums) {
final int len = pageNums.length;
for (int i = 0; i < len; i++) {
startup_markPageAsUsed(pageNums[i]);
}
}
/** we will clear pageIndex In this method
* page index will be used only to cache dirty pages after startup
*/
void startup_complete() {
STARTUP_FILE_BUFF.clear();
STARTUP_FILE_BUFF = null;
for (int i = pageIndex.length; --i >= 0;) {
if (pageIndex[i] == null) { // is not used
freePageNums.add(i);
} else {
pageIndex[i] = null;
}
}
}
boolean startup_isPageUsed(int pageNo) {
return pageIndex[pageNo] != null;
}
/**
* resulted byte array instance is reuseed by this method with every method call
*/
byte[] startup_readPage(final int pageNo) {
STARTUP_FILE_BUFF.position(pageNo << pageSizeBits);
STARTUP_FILE_BUFF.get(STARTUP_TMP_PAGE);
return STARTUP_TMP_PAGE;
}
/**
* WARN: do not reuse data param passed to this method from outside!!!! use getPageImage instead
* + use only byte[] data from getPageImage for this method! page images are pooled
* the only other value allowed (except derived from getPageImage) is this.ZERO_PAGE!!
* @param pageNo
* @param data
*/
void writePage(final int pageNo, final byte[] data) {
assert(pageIndex[pageNo] == null || pageIndex[pageNo] == ZERO_PAGE);
if (pageIndex[pageNo] == null) {
dirtyPages.add(pageNo);
}
pageIndex[pageNo] = data;
}
synchronized int allocatePage() {
if (freePageNums.isEmpty()) {
extend();
}
final int pageNo = freePageNums.removeLast();
assert(pageIndex[pageNo] == null || pageIndex[pageNo] == ZERO_PAGE);
return pageNo;
}
private void extend() {
final int oldNumberOfPages = pageIndex.length;
final int newNumberOfPages = (int) (oldNumberOfPages * extentionQuantum);
freePageNums.ensureCapacity(freePageNums.getSize() + newNumberOfPages - oldNumberOfPages);
for (int i = newNumberOfPages; --i >= oldNumberOfPages;) {
freePageNums.add(i);
}
final Object[] newPageIndex = new Object[newNumberOfPages];
System.arraycopy(pageIndex, 0, newPageIndex, 0, pageIndex.length);
pageIndex = newPageIndex;
}
void deallocatePages(final int[] pageNums) {
final int len = pageNums.length;
for (int i = 0; i < len; i++) {
deallocatePage(pageNums[i]);
}
}
/**
* page with pageNo will now free to use.
*/
void deallocatePage(final int pageNo) {
// can't deallocate dirty page
// page should not be used to deallocate
assert(pageIndex[pageNo] == null || pageIndex[pageNo] == ZERO_PAGE);
pageIndex[pageNo] = ZERO_PAGE;
dirtyPages.add(pageNo);
freePageNums.add(pageNo);
}
long getFileSize() throws IOException {
return file.length();
}
int getNumberOfPages() {
return pageIndex.length;
}
/**
* coeff to extent
* @param quantum
*/
void setExtentionQuantim(final int quantum) {
extentionQuantum = quantum;
}
synchronized void flush() throws IOException {
long time = System.currentTimeMillis();
Arrays.sort(dirtyPages.data, 0, dirtyPages.getSize());
if (Log.isDebugEnabled()) {
Log.debug("pages sort time:" + (System.currentTimeMillis() - time));
}
final int size = dirtyPages.getSize();
for (int i = 0; i < size; i++) {
final int pageNo = dirtyPages.data[i];
file.seek(getPageOffset(pageNo));
final byte[] pageImage = (byte[]) pageIndex[pageNo];
file.write(pageImage);
pageIndex[pageNo] = null;
if (pageImage != ZERO_PAGE) {
pageImagePool.add(pageImage);
}
}
time = System.currentTimeMillis();
file.getFD().sync();
if (Log.isDebugEnabled()) {
Log.debug("flush time:" + (System.currentTimeMillis() - time));
}
dirtyPages.clear();
}
private long getPageOffset(final int pageNo) {
return FIRST_PAGE_OFFSET + (pageNo << pageSizeBits);
}
void close() throws IOException {
file.close();
}
byte[] getPageImage() {
if (pageImagePool.isEmpty()) {
final int oldSize = pageImagePool.capacity();
pageImagePool.ensureCapacity(pageImagePool.data.length * 2);
for (int i = 0; i < oldSize; i++) {
pageImagePool.add(new byte[pageSize]);
}
}
return (byte[]) pageImagePool.removeLast();
}
String getFileName() {
return new File(fileName).getAbsolutePath();
}
RandomAccessFile getFile() {
return file;
}
}