package net.sourceforge.pain.db; import net.sourceforge.pain.util.*; import java.io.*; 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 NEW_FILE_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 freePages; /** 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; private int pageSizeBits; private float extentionQuantum = 1.3F; private final String fileName; DbPageMapper(final String fileName) throws IOException { this.fileName = fileName; file = new RandomAccessFile(fileName, "rw"); readMapperMetaInfo(); freePages = new DbIntBuffer((int) (file.length() / pageSize)); ZERO_PAGE = new byte[pageSize]; // startup optimization, all file cached cacheFile(); pageImagePool = new DbObjBuffer(4096); for (int i = pageImagePool.getSize(); --i >= 0;) { pageImagePool.add(new byte[pageSize]); } } private void cacheFile() throws IOException { file.seek(FIRST_PAGE_OFFSET); final int numberOfPages = getNumberOfPages(); for (int i = 0; i < numberOfPages; i++) { final byte[] page = new byte[pageSize]; file.read(page); pageIndex[i] = page; } } 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 = NEW_FILE_PAGES; file.setLength(FIRST_PAGE_OFFSET + NEW_FILE_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); } /** * Warn: after page marked as used during startup we should not ask it's value again * @param pageNo */ void startup_markPageAsUsed(final int pageNo) { pageIndex[pageNo] = null; } void startup_markPageAsUsed(final int[] pageNums) { for (int i = 0; i < pageNums.length; i++) { pageIndex[pageNums[i]] = null; } } /** we will clear pageIndex In this method * page index will be used only to cache dirty pages after startup */ void startup_complete() { for (int i = pageIndex.length; --i >= 0;) { if (pageIndex[i] != null) { // not used freePages.add(i); pageIndex[i] = null; } } } byte[] startup_readPage(final int pageNo) { return (byte[]) pageIndex[pageNo]; } /** * 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) { PAssert.that(pageIndex[pageNo] == null); pageIndex[pageNo] = data; dirtyPages.add(pageNo); } synchronized int allocatePage() { if (freePages.isEmpty()) { extend(); } return freePages.removeLast(); } private void extend() { final int oldNumberOfPages = pageIndex.length; final int newNumberOfPages = (int) (oldNumberOfPages * extentionQuantum); freePages.ensureCapacity(freePages.getSize() + newNumberOfPages - oldNumberOfPages); for (int i = newNumberOfPages; --i >= oldNumberOfPages;) { freePages.add(i); } final Object[] newPageIndex = new Object[newNumberOfPages]; System.arraycopy(pageIndex, 0, newPageIndex, 0, pageIndex.length); pageIndex = newPageIndex; } void deallocatePages(final int[] pageNums) { for (int i = 0; i < pageNums.length; 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 PAssert.that(pageIndex[pageNo] == null); freePages.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]); } } final byte[] result = (byte[]) pageImagePool.removeLast(); result[0] = 0; //todo : do we still need this? return result; } String getFileName() { return new File(fileName).getPath(); } }