/
area/
classes/net/sourceforge/pain/console/
classes/net/sourceforge/pain/logic/
classes/net/sourceforge/pain/logic/event/
classes/net/sourceforge/pain/logic/fn/util/
classes/net/sourceforge/pain/plugin/
classes/net/sourceforge/pain/plugin/reset/
classes/net/sourceforge/pain/plugin/shutdown/
classes/net/sourceforge/pain/plugin/social/
classes/net/sourceforge/pain/util/
classest/net/sourceforge/pain/db/data/
doc/
doc/paindb/resources/
src/net/sourceforge/pain/console/
src/net/sourceforge/pain/console/telnet/
src/net/sourceforge/pain/logic/
src/net/sourceforge/pain/logic/event/
src/net/sourceforge/pain/logic/fn/util/
src/net/sourceforge/pain/plugin/
src/net/sourceforge/pain/plugin/command/
src/net/sourceforge/pain/plugin/reset/
src/net/sourceforge/pain/plugin/shutdown/
src/net/sourceforge/pain/plugin/social/
src/net/sourceforge/pain/util/
tests/net/sourceforge/pain/db/data/
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();
	}

}