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