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; } }