/
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 org.xml.sax.*;
import org.xml.sax.helpers.*;


/**
 * User: fmike  Date: 12.03.2003  Time: 18:31:56
 * Maps objects to pages
 *
 *
 *
 * PAGE STRUCTURE:
 * HEADER START
 * 1 byte: 1 bit - is object start, 2 bit - first  4 bytes after header is nextPageNo
 * 2-5  bytes - SCHEMA_ID
 * 6-17 bytes - OID
 * HEADER END
 * image
 * 4 bytes (PageNo) if flag ok
 *
 *
 */
final class DbObjectMapper {

	private static final byte OBJECT_START_BIT = 1;
	private static final byte NEXT_PAGE_PRESENT_BIT = 2;

	private final int NEXT_PAGE_NUM_OFFSET;
	private final int PAGE_SIZE;
	private final PainDB db;
	private final DbPageMapper pager;

	private final DbByteBuffer objImageBuf = new DbByteBuffer(1024);
	private final DbIntBuffer readPageNumsBuf = new DbIntBuffer(128);
	private char[] charBuf = new char[4096];

	private static final int[] ZERO_INT_ARRAY = new int[0];
	private static final char[] ZERO_CHAR_ARRAY = new char[0];
	private static final byte[] ZERO_BYTE_ARRAY = new byte[0];
	private static final String[] ZERO_STRING_ARRAY = new String[0];

	DbObjectMapper(final PainDB db, final DbPageMapper mapper) {
		this.db = db;
		this.pager = mapper;
		PAGE_SIZE = mapper.pageSize;
		NEXT_PAGE_NUM_OFFSET = PAGE_SIZE - 4;
	}

	DbRuntimeClass readClassSchema(final int startPageNo) throws Exception {
		final DbClassImage obj = (DbClassImage) readObject(startPageNo);
		return new DbRuntimeClass(obj);
	}


	DbObject readObject(final int startPageNo) throws Exception {
		readObjectImage(startPageNo);
		final byte[] data = objImageBuf.data; //synchr. start
		final int[] pageNums = new int[readPageNumsBuf.getSize()];
		System.arraycopy(readPageNumsBuf.data, 0, pageNums, 0, readPageNumsBuf.getSize());
		final int classId = DbPacker.unpack4(data, 0);
		final DbOid oid = new DbOid(DbPacker.unpack4(data, 4), DbPacker.unpack8(data, 8));
		final DbClassImpl dbClass;
		if (classId == oid.indexId) { // class desc -> hardcoded dbClass
			dbClass = db.getDbClassMetaSchema();
		} else { // simple object -> should find dbClass
			dbClass = db.getDbClassSchema(classId);
		}
		final DbObject obj = db.reflectDbObject(dbClass, oid, pageNums);
		final int dataIndex = obj.dataIndex;

		final byte[] types = dbClass.getFieldTypes();
		final MappingPointer p = new MappingPointer(16);
		final int len = types.length;
		for (int i = 0; i < len; i++) {
			final byte type = types[i];
			final Object values = dbClass.data.fieldsValues[i];
			switch (type) {
				case DbType.BOOLEAN:
					((boolean[]) values)[dataIndex] = readBoolean(data, p);
					break;
				case DbType.BYTE:
					((byte[]) values)[dataIndex] = readByte(data, p);
					break;
				case DbType.CHAR:
					((char[]) values)[dataIndex] = readChar(data, p);
					break;
				case DbType.DOUBLE:
					((double[]) values)[dataIndex] = readDouble(data, p);
					break;
				case DbType.FLOAT:
					((float[]) values)[dataIndex] = readFloat(data, p);
					break;
				case DbType.INT:
					((int[]) values)[dataIndex] = readInt(data, p);
					break;
				case DbType.LONG:
					((long[]) values)[dataIndex] = readLong(data, p);
					break;
				case DbType.SHORT:
					((short[]) values)[dataIndex] = readShort(data, p);
					break;
				case DbType.REFERENCE:
					((int[]) ((Object[]) values)[0])[dataIndex] = readInt(data, p);
					((long[]) ((Object[]) values)[1])[dataIndex] = readLong(data, p);
					break;
				case DbType.STRING:
					((String[]) values)[dataIndex] = readString(data, p);
					break;
				case DbType.ARRAY_OF_BYTE:
					((Object[]) values)[dataIndex] = readByteArray(data, p);
					break;
				case DbType.ARRAY_OF_CHAR:
					((Object[]) values)[dataIndex] = readCharArray(data, p);
					break;
				case DbType.ARRAY_OF_INT:
					((Object[]) values)[dataIndex] = readIntArray(data, p);
					break;
				case DbType.ARRAY_OF_STRING:
					((Object[]) values)[dataIndex] = readStringArray(data, p);
					break;
				case DbType.LINKED_LIST:
				case DbType.ARRAY_LIST:
				case DbType.INT_KEY_MAP:
				case DbType.REFERENCE_SET:
					/**
					 * Collection instance will be instantiated latter, after all objects will loaded
					 * before this it slot will be used with it's image
					 */
					((Object[]) values)[dataIndex] = readIntArray(data, p);
					break;
				case DbType.STRING_KEY_MAP:
					((Object[]) values)[dataIndex] = readStringKeyMap(data, p);
					break;
				case DbType.STRING_SET:
				case DbType.STRING_MAP:
					((Object[]) values)[dataIndex] = readStringArray(data, p);
					break;
				default:
					throw new RuntimeException("not valid type:" + type);
			}
		}
		return obj;
	}


	private Object readStringKeyMap(byte[] data, MappingPointer p) {
		String[] keys = readStringArray(data, p);
		int[] values = readIntArray(data, p);
		return new Object[]{keys, values};
	}


	void writeObject(final DbObject obj) {
		final DbClassImpl dbClass = obj.dbClass;
		final byte[] types = dbClass.getFieldTypes();
		final int dataIndex = obj.dataIndex;
		objImageBuf.clear();
		objImageBuf.add(db.getClassId(obj));
		objImageBuf.add(obj.indexId);
		objImageBuf.add(obj.versionId);
		final int len = types.length;
		for (int i = 0; i < len; i++) {
			final byte type = types[i];
			final Object values = dbClass.data.fieldsValues[i];
			switch (type) {
				case DbType.BOOLEAN:
					writeBoolean(objImageBuf, ((boolean[]) values)[dataIndex]);
					break;
				case DbType.BYTE:
					writeByte(objImageBuf, ((byte[]) values)[dataIndex]);
					break;
				case DbType.CHAR:
					writeChar(objImageBuf, ((char[]) values)[dataIndex]);
					break;
				case DbType.DOUBLE:
					writeDouble(objImageBuf, ((double[]) values)[dataIndex]);
					break;
				case DbType.FLOAT:
					writeFloat(objImageBuf, ((float[]) values)[dataIndex]);
					break;
				case DbType.INT:
					writeInt(objImageBuf, ((int[]) values)[dataIndex]);
					break;
				case DbType.LONG:
					writeLong(objImageBuf, ((long[]) values)[dataIndex]);
					break;
				case DbType.SHORT:
					writeShort(objImageBuf, ((short[]) values)[dataIndex]);
					break;
				case DbType.STRING:
					writeString(objImageBuf, ((String[]) values)[dataIndex]);
					break;
				case DbType.REFERENCE:
					writeInt(objImageBuf, ((int[]) ((Object[]) values)[0])[dataIndex]);
					writeLong(objImageBuf, ((long[]) ((Object[]) values)[1])[dataIndex]);
					break;
				case DbType.ARRAY_OF_BYTE:
					writeByteArray(objImageBuf, (byte[]) ((Object[]) values)[dataIndex]);
					break;
				case DbType.ARRAY_OF_CHAR:
					writeCharArray(objImageBuf, (char[]) ((Object[]) values)[dataIndex]);
					break;
				case DbType.ARRAY_OF_INT:
					writeIntArray(objImageBuf, (int[]) ((Object[]) values)[dataIndex]);
					break;
				case DbType.ARRAY_OF_STRING:
					writeStringArray(objImageBuf, (String[]) ((Object[]) values)[dataIndex]);
					break;
				case DbType.LINKED_LIST:
					writeLinkedList(objImageBuf, (DbLinkedList) ((Object[]) values)[dataIndex]);
					break;
				case DbType.ARRAY_LIST:
					writeArrayList(objImageBuf, (DbArrayList) ((Object[]) values)[dataIndex]);
					break;
				case DbType.INT_KEY_MAP:
					writeIntKeyMap(objImageBuf, (DbIntKeyMap) ((Object[]) values)[dataIndex]);
					break;
				case DbType.STRING_KEY_MAP:
					writeStringKeyMap(objImageBuf, (DbStringKeyMap) ((Object[]) values)[dataIndex]);
					break;
				case DbType.REFERENCE_SET:
					writeReferenceSet(objImageBuf, (DbReferenceSet) ((Object[]) values)[dataIndex]);
					break;
				case DbType.STRING_SET:
				case DbType.STRING_MAP:
					Object o = ((Object[]) values)[dataIndex];
					if (o instanceof DbAbstractStringMap) {
						if (type == DbType.STRING_SET) {
							writeStringSet(objImageBuf, (DbStringSet) o);
						} else {
							writeStringMap(objImageBuf, (DbStringMap) o);
						}
					} else {
						writeStringArray(objImageBuf, (String[]) o);
					}
					break;
				default:
					throw new RuntimeException("not valid type:" + type);
			}
		}

		final int bufSize = objImageBuf.getSize();
		int nPages = 1 + bufSize / (PAGE_SIZE - 1 - 4);
		if (nPages > 1 && bufSize % (PAGE_SIZE - 1 - 4) < 4) { // if we have only 4 bytes on the last page we could put this data on the NEXT_PAGE_LINK place of the prev page
			nPages--;
		}
		int availablePages = obj.pageNums.length;
		if (nPages != availablePages) {
			final int[] oldPageNums = obj.pageNums;
			final int[] newPageNums = new int[nPages];
			if (nPages < availablePages) {
				System.arraycopy(oldPageNums, 0, newPageNums, 0, nPages);
				while (nPages < availablePages) {
					availablePages--;
					pager.deallocatePage(oldPageNums[availablePages]);
				}
			} else {
				if (availablePages != 0) {
					System.arraycopy(oldPageNums, 0, newPageNums, 0, availablePages);
				}
				while (availablePages < nPages) {
					newPageNums[availablePages] = pager.allocatePage();
					availablePages++;
				}
			}
			obj.pageNums = newPageNums;
		}

		final int[] pages = obj.pageNums;
		byte[] pageData = pager.getPageImage();
		pageData[0] = OBJECT_START_BIT;
		for (int offset = 0, pageNumIndex = 0; offset < bufSize; pageNumIndex++) {
			if (pageNumIndex < nPages - 1) { // if not last page
				pageData[0] = (byte) (pageData[0] | NEXT_PAGE_PRESENT_BIT);
				System.arraycopy(objImageBuf.data, offset, pageData, 1, PAGE_SIZE - 5);
				DbPacker.pack4(pageData, PAGE_SIZE - 4, pages[pageNumIndex + 1]);
				pager.writePage(pages[pageNumIndex], pageData);
				pageData = pager.getPageImage();
				pageData[0] = 0;
				offset += PAGE_SIZE - 5;
			} else {
				final int dataSize = bufSize - offset;
				System.arraycopy(objImageBuf.data, offset, pageData, 1, dataSize);
				pager.writePage(pages[pageNumIndex], pageData);
				break;
			}
		}
	}


	private void writeStringArray(final DbByteBuffer buf, final String[] strings) {
		int len;
		if (strings == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if ((len = strings.length) > 0) {
			buf.add(len);
			for (int i = 0; i < len; i++) {
				writeString(buf, strings[i]);
			}
		} else { // empty array
			buf.add(0);
			writeBoolean(buf, false);
		}
	}

	private static void writeLinkedList(final DbByteBuffer buf, final DbLinkedList l) {
		final int size;
		if (l == null || (size = l._size()) == 0) { // lazy instantiation allows it
			writeIntArray(buf, null);
		} else {
			final DbLinkedList.Entry first = l.getFirstEntry();
			buf.prepareToAdd(4 + 4 * size);
			buf.add(size);
			for (DbLinkedList.Entry runner = first; runner != null; runner = runner.nextInList) {
				buf._add(runner.obj.indexId);
			}
		}
	}

	private static void writeArrayList(final DbByteBuffer buf, final DbArrayList l) {
		final int size;
		if (l == null || (size = l._size()) == 0) { // lazy instantiation allows it
			writeIntArray(buf, null);
		} else {
			final DbArrayList.Entry[] items = l.getItems();
			buf.prepareToAdd(4 + 4 * size);
			buf.add(size);
			for (int i = 0; i < size; i++) {
				final DbArrayList.Entry e = items[i];
				buf._add(e.obj == null ? -1 : e.obj.indexId);
			}
		}
	}

	private static void writeIntKeyMap(final DbByteBuffer buf, final DbIntKeyMap m) {
		final int size;
		if (m == null || (size = m._size()) == 0) { // lazy instantiation allows it
			writeIntArray(buf, null);
		} else {
			buf.prepareToAdd(4 + 8 * size);
			buf.add(size * 2);
			final DbAbstractMap.AMapEntry data[] = m._getData();
			final int len = data.length;
			for (int i = 0; i < len; i++) {
				for (DbIntKeyMap.AMapEntry e = data[i]; e != null; e = e.next) {
					final DbObject obj = e.obj;
					buf.add(((DbIntKeyMap.IntKeyMapEntry) e).key);
					buf.add(obj.indexId); // map can't contains null values
				}
			}
		}
	}

	private void writeStringSet(DbByteBuffer buf, DbStringSet set) {
		final int size;
		if ((size = set._size()) == 0) {
			writeStringArray(buf, ZERO_STRING_ARRAY);
		} else {
			buf.prepareToAdd(4 + 16 * size);
			buf.add(size); //4
			final DbAbstractStringMap.AStringMapEntry data[] = set._getData();
			final int len = data.length;
			for (int i = 0; i < len; i++) {
				for (DbAbstractStringMap.AStringMapEntry e = data[i]; e != null; e = e.next) {
					writeString(buf, e.key);
				}
			}
		}
	}

	private void writeStringMap(DbByteBuffer buf, DbStringMap map) {
		final int size;
		if ((size = map._size()) == 0) {
			writeStringArray(buf, ZERO_STRING_ARRAY);
		} else {
			buf.prepareToAdd(4 + 16 * size);
			buf.add(size * 2); //4  bytes, map image array len
			final DbAbstractStringMap.AStringMapEntry data[] = map._getData();
			final int len = data.length;
			for (int i = 0; i < len; i++) {
				for (DbAbstractStringMap.AStringMapEntry e = data[i]; e != null; e = e.next) {
					writeString(buf, e.key);
					writeString(buf, e.value);
				}
			}
		}
	}

	private void writeStringKeyMap(DbByteBuffer buf, DbStringKeyMap m) {
		final int size;
		if (m == null || (size = m._size()) == 0) { // lazy instantiation allows it
			writeStringArray(buf, ZERO_STRING_ARRAY);
			writeIntArray(buf, ZERO_INT_ARRAY);
		} else {
			buf.prepareToAdd(4 + 16 * size);
			// writing keys
			buf.add(size);
			final DbStringKeyMap.AMapEntry data[] = m._getData();
			final int len = data.length;
			for (int i = 0; i < len; i++) {
				for (DbAbstractMap.AMapEntry e = data[i]; e != null; e = e.next) {
					writeString(buf, ((DbStringKeyMap.StringKeyMapEntry) e).key);
				}
			}
			// writing values
			buf.add(size);
			for (int i = 0; i < len; i++) {
				for (DbStringKeyMap.AMapEntry e = data[i]; e != null; e = e.next) {
					buf.add(e.obj.indexId);
				}
			}
		}
	}


	private static void writeReferenceSet(final DbByteBuffer buf, final DbReferenceSet s) {
		final int size;
		if (s == null || (size = s._size()) == 0) { // lazy instantiation allows it
			writeIntArray(buf, null);
		} else {
			buf.prepareToAdd(4 + 4 * size);
			buf.add(size);
			final DbAbstractMap.AMapEntry data[] = s._getData();
			final int len = data.length;
			for (int i = 0; i < len; i++) {
				for (DbAbstractMap.AMapEntry e = data[i]; e != null; e = e.next) {
					buf.add(e.obj.indexId); // reference does not contain nulls
				}
			}
		}
	}

	private static void writeIntArray(final DbByteBuffer buf, final int[] ints) {
		if (ints == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if (ints.length > 0) {
			buf.add(ints.length);
			buf.add(ints);
		} else { // empty array
			buf.add(0);
			writeBoolean(buf, false);
		}
	}

	private static void writeByteArray(final DbByteBuffer buf, final byte[] bytes) {
		if (bytes == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if (bytes.length > 0) {
			buf.add(bytes.length);
			buf.add(bytes);
		} else { // empty array
			buf.add(0);
			writeBoolean(buf, false);
		}
	}

	private static void writeCharArray(final DbByteBuffer buf, final char[] chars) {
		if (chars == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if (chars.length > 0) {
			buf.add(chars.length);
			buf.add(chars);
		} else { // empty array
			buf.add(0);
			writeBoolean(buf, false);
		}
	}

	private void writeString(final DbByteBuffer buf, final String s) {
		int len;
		if (s == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if ((len = s.length()) > 0) {
			buf.add(len);
			if (charBuf.length < len) {
				charBuf = new char[len];
			}
			s.getChars(0, len, charBuf, 0);
			buf.add(charBuf, 0, len);
		} else { // empty string
			buf.add(0);
			writeBoolean(buf, false);
		}
	}

	private static void writeFloat(final DbByteBuffer buf, final float v) {
		buf.add(Float.floatToIntBits(v));
	}

	private static void writeDouble(final DbByteBuffer buf, final double v) {
		buf.add(Double.doubleToLongBits(v));
	}

	private static void writeInt(final DbByteBuffer buf, final int v) {
		buf.add(v);
	}

	private static void writeLong(final DbByteBuffer buf, final long v) {
		buf.add(v);
	}

	private static void writeShort(final DbByteBuffer buf, final short v) {
		buf.add(v);
	}

	private static void writeChar(final DbByteBuffer buf, final char v) {
		buf.add((short) v);
	}

	private static void writeByte(final DbByteBuffer buf, final byte v) {
		buf.add(v);
	}

	private static void writeBoolean(final DbByteBuffer buf, final boolean b) {
		buf.add((byte) (b ? 1 : 0));
	}

	private static long readLong(final byte[] data, final MappingPointer p) {
		final long result = DbPacker.unpack8(data, p.pos);
		p.pos += 8;
		return result;
	}

	private static int readInt(final byte[] data, final MappingPointer p) {
		final int result = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		return result;
	}

	private static float readFloat(final byte[] data, final MappingPointer p) {
		final float result = Float.intBitsToFloat(DbPacker.unpack4(data, p.pos));
		p.pos += 4;
		return result;
	}

	private static double readDouble(final byte[] data, final MappingPointer p) {
		final double result = Double.longBitsToDouble(DbPacker.unpack8(data, p.pos));
		p.pos += 8;
		return result;
	}

	private static char readChar(final byte[] data, final MappingPointer p) {
		final char result = (char) DbPacker.unpack2(data, p.pos);
		p.pos += 2;
		return result;
	}

	private static short readShort(final byte[] data, final MappingPointer p) {
		final short result = DbPacker.unpack2(data, p.pos);
		p.pos += 2;
		return result;
	}

	private static byte readByte(final byte[] data, final MappingPointer p) {
		final byte result = data[p.pos];
		p.pos++;
		return result;
	}


	private static boolean readBoolean(final byte[] data, final MappingPointer p) {
		final boolean result = data[p.pos] == 1;
		p.pos++;
		return result;
	}

	private String[] readStringArray(final byte[] data, final MappingPointer p) {
		final int len = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		if (len == 0) {
			final boolean isNull = readBoolean(data, p);
			if (isNull) {
				return null;
			} else {
				return ZERO_STRING_ARRAY;
			}
		} else {
			final String[] result = new String[len];
			for (int i = 0; i < len; i++) {
				result[i] = readString(data, p);
			}
			return result;
		}
	}

	private static byte[] readByteArray(final byte[] data, final MappingPointer p) {
		final int len = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		if (len == 0) {
			final boolean isNull = readBoolean(data, p);
			if (isNull) {
				return null;
			} else {
				return ZERO_BYTE_ARRAY;
			}
		} else {
			final byte[] result = new byte[len];
			for (int i = 0, j = p.pos; i < len; i++, j += 1) {
				result[i] = data[j];
			}
			p.pos += len;
			return result;
		}
	}

	private static int[] readIntArray(final byte[] data, final MappingPointer p) {
		final int len = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		if (len == 0) {
			final boolean isNull = readBoolean(data, p);
			if (isNull) {
				return null;
			} else {
				return ZERO_INT_ARRAY;
			}
		} else {
			final int[] result = new int[len];
			for (int i = 0, j = p.pos; i < len; i++, j += 4) {
				result[i] = DbPacker.unpack4(data, j);
			}
			p.pos += len * 4;
			return result;
		}
	}

	private static char[] readCharArray(final byte[] data, final MappingPointer p) {
		final int len = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		if (len == 0) {
			final boolean isNull = readBoolean(data, p);
			if (isNull) {
				return null;
			} else {
				return ZERO_CHAR_ARRAY;
			}
		} else {
			final char[] result = new char[len];
			for (int i = 0, j = p.pos; i < len; i++, j += 2) {
				result[i] = (char) DbPacker.unpack2(data, j);
			}
			p.pos += len << 1;
			return result;
		}
	}

	private String readString(final byte[] data, final MappingPointer p) {
		final int len = DbPacker.unpack4(data, p.pos);
		p.pos += 4;
		if (len == 0) {
			final boolean isNull = readBoolean(data, p);
			if (isNull) {
				return null;
			} else {
				return "";
			}
		} else {
			if (charBuf.length < len) {
				charBuf = new char[len];
			}
			for (int i = 0, j = p.pos; i < len; i++, j += 2) {
				charBuf[i] = (char) DbPacker.unpack2(data, j);
			}
			final String result = new String(charBuf, 0, len);
			p.pos += len * 2;
			return result;
		}
	}

	/**
	 * WARN: single threaded model!
	 * @param startPageNo
	 */

	private void readObjectImage(final int startPageNo) {
		objImageBuf.clear();
		readPageNumsBuf.clear();
		int nextPageNum = startPageNo;
		byte[] data;
		do {
			readPageNumsBuf.add(nextPageNum);
			data = pager.startup_readPage(nextPageNum);
			assert((nextPageNum == startPageNo) ? (data[0] & OBJECT_START_BIT) > 0 : (data[0] & OBJECT_START_BIT) == 0);
			nextPageNum = ((data[0] & NEXT_PAGE_PRESENT_BIT) > 0) ? DbPacker.unpack4(data, NEXT_PAGE_NUM_OFFSET) : 0;
			objImageBuf.addFromTo(data, 1, (nextPageNum != 0 ? NEXT_PAGE_NUM_OFFSET : PAGE_SIZE));
		} while (nextPageNum > 0);
	}


	/**
	 * @return true if this page is class start page and not object start page
	 */
	static boolean isClassSchemaStartPage(final byte[] data) {
		return (data[0] & OBJECT_START_BIT) > 0 && (DbPacker.unpack4(data, 1) == DbPacker.unpack4(data, 5));
	}

	/**
	 * @return  true if this page is object start page and not class start page
	 */
	static boolean isObjectStartPage(final byte[] data) {
		return (data[0] & OBJECT_START_BIT) > 0 && (DbPacker.unpack4(data, 1) != DbPacker.unpack4(data, 5));
	}

	static final class MappingPointer {
		int pos;

		MappingPointer(final int pos) {
			this.pos = pos;
		}
	}

	/**
	 * used by paindb during xml backups.
	 * for binary objects (Strings are also binary since it can contains 0 chars) - returns BASE64 encoded values
	 */
	String getXMLValue(byte[] fieldTypes, int i, DbClassImpl dbClass, DbObject obj) {
		objImageBuf.clear();
		final Object values = dbClass.data.fieldsValues[i];
		String result;
		final int dataIndex = obj.dataIndex;
		final byte type = fieldTypes[i];
		switch (type) {
			case DbType.BOOLEAN:
				result = ((boolean[]) values)[dataIndex] ? "true" : "false";
				break;
			case DbType.BYTE:
				result = "" + ((byte[]) values)[dataIndex];
				break;
			case DbType.CHAR:
				result = "" + (int) ((char[]) values)[dataIndex];
				break;
			case DbType.DOUBLE:
				result = "" + ((double[]) values)[dataIndex];
				break;
			case DbType.FLOAT:
				result = "" + ((float[]) values)[dataIndex];
				break;
			case DbType.INT:
				result = "" + ((int[]) values)[dataIndex];
				break;
			case DbType.LONG:
				result = "" + ((long[]) values)[dataIndex];
				break;
			case DbType.SHORT:
				result = "" + ((short[]) values)[dataIndex];
				break;
			case DbType.REFERENCE:
				int indexId = ((int[]) ((Object[]) values)[0])[dataIndex];
				DbObject ref = indexId >= 0 ? db.getObjectByIndexId(indexId) : null;
				if (ref != null) {
					long versionId = ((long[]) ((Object[]) values)[1])[dataIndex];
					if (versionId == ref.versionId) {
						result = DbOid.toString(indexId, versionId);
						break;
					}
				}
				result = "null";
				break;
				// all futher code is copied from writeObject method!!! (should be synchronized)
			case DbType.STRING:
				writeString(objImageBuf, ((String[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.ARRAY_OF_BYTE:
				writeByteArray(objImageBuf, (byte[]) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.ARRAY_OF_CHAR:
				writeCharArray(objImageBuf, (char[]) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.ARRAY_OF_INT:
				writeIntArray(objImageBuf, (int[]) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.ARRAY_OF_STRING:
				writeStringArray(objImageBuf, (String[]) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.LINKED_LIST:
				writeLinkedList(objImageBuf, (DbLinkedList) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.ARRAY_LIST:
				writeArrayList(objImageBuf, (DbArrayList) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.INT_KEY_MAP:
				writeIntKeyMap(objImageBuf, (DbIntKeyMap) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.STRING_KEY_MAP:
				writeStringKeyMap(objImageBuf, (DbStringKeyMap) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.REFERENCE_SET:
				writeReferenceSet(objImageBuf, (DbReferenceSet) ((Object[]) values)[dataIndex]);
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			case DbType.STRING_SET:
			case DbType.STRING_MAP:
				Object o = ((Object[]) values)[dataIndex];
				if (o instanceof DbAbstractStringMap) {
					if (type == DbType.STRING_SET) {
						writeStringSet(objImageBuf, (DbStringSet) o);
					} else {
						writeStringMap(objImageBuf, (DbStringMap) o);
					}
				} else {
					writeStringArray(objImageBuf, (String[]) o);
				}
				result = Base64.encodeBytes(objImageBuf.data, 0, objImageBuf.getSize());
				break;
			default:
				throw new RuntimeException("not valid type:" + type);

		}
		return result;
	}

	XMLImportDataHandler createXmlImportDataHandler() {
		return new XMLImportDataHandler();
	}

	final class XMLImportDataHandler extends DefaultHandler {
		private static final int STATE_WAIT_ROOT = 1;
		private static final int STATE_WAIT_CLASS_START = 2;
		private static final int STATE_WAIT_CLASS_END = 3;
		private static final int STATE_WAIT_OBJECT_START = 4;
		private static final int STATE_WAIT_OBJECT_END = 5;
		private static final int STATE_FINISHED = 6;

		public static final String rootTag = "paindb";
		public static final String classTag = "class";
		public static final String objectTag = "object";
		public static final String fieldTag = "field";
		public static final String metadataTag = "metadata";
		public static final String objectsTag = "objects";


		private int state = STATE_WAIT_ROOT;

		private XMLClassData classData = new XMLClassData();
		private XMLObjectData objectData = new XMLObjectData();
		String rootOid = null;
		int nItems;
		int maxUsedIndexId;

		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
			qName = qName.toLowerCase();
			switch (state) {
				case STATE_WAIT_ROOT:
					if (rootTag.equals(qName)) {
						rootOid = attributes.getValue("rootOid");
						maxUsedIndexId = Integer.parseInt(attributes.getValue("maxIndex"));
						nItems = Integer.parseInt(attributes.getValue("nItems"));
						db.extendObjectsIndex(maxUsedIndexId + 1, false);
					} else if (metadataTag.equals(qName)) {
						state = STATE_WAIT_CLASS_START;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_CLASS_START:
					if (classTag.equals(qName)) {
						classData.clear();
						classData.name = attributes.getValue("name");
						classData.oid = attributes.getValue("id");
						state = STATE_WAIT_CLASS_END;
					} else if (metadataTag.equals(qName)) {
						state = STATE_WAIT_OBJECT_START;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_CLASS_END:
					if (fieldTag.equals(qName)) {
						classData.names.add(attributes.getValue("name"));
						classData.types.add(Byte.parseByte(attributes.getValue("type")));
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_OBJECT_START:
					if (objectTag.equals(qName)) {
						objectData.clear();
						objectData.className = attributes.getValue("class");
						objectData.oid = attributes.getValue("id");
						state = STATE_WAIT_OBJECT_END;
					} else if (objectsTag.equals(qName)) {
						break;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_OBJECT_END:
					if (fieldTag.equals(qName)) {
						objectData.fieldValues.add(attributes.getValue("value"));
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_FINISHED:
					throw new RuntimeException("elements after document end!:" + qName);
			}
		}

		public void endElement(String uri, String localName, String qName) throws SAXException {
			qName = qName.toLowerCase();
			switch (state) {
				case STATE_WAIT_ROOT:
					throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
				case STATE_WAIT_CLASS_START:
					if (metadataTag.equals(qName)) {
						state = STATE_WAIT_OBJECT_START;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_CLASS_END:
					if (fieldTag.equals(qName)) {
						break;
					} else if (classTag.equals(qName)) {
						try {
							registerClass();
						} catch (Exception e) {
							throw new RuntimeException(e);
						}
						state = STATE_WAIT_CLASS_START;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_OBJECT_START:
					if (objectsTag.equals(qName)) {
						state = STATE_FINISHED;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_WAIT_OBJECT_END:
					if (fieldTag.equals(qName)) {
						break;
					} else if (objectTag.equals(qName)) {
						try {
							registerObject();
						} catch (Exception e) {
							throw new RuntimeException(e);
						}
						state = STATE_WAIT_OBJECT_START;
					} else {
						throw new SAXNotRecognizedException("Illegal element name or sequence:" + qName);
					}
					break;
				case STATE_FINISHED:
					if (!rootTag.equals(qName)) {
						throw new RuntimeException("elements after document end!:" + qName);
					}
			}
		}

		// debug
//		int nClasses = 0;
//		int nObjects = 0;

		private void registerClass() throws Exception {
//			nClasses++;
			db.registerNewClass(Class.forName(classData.name), new DbClassSchema(classData.types.toArray(), classData.names.toArray()), new DbOid(classData.oid));
		}

		private void registerObject() throws Exception {
//			nObjects++;
			DbRuntimeClass dbClass = db.getDbClassByClassName(objectData.className);
			final byte[] types = dbClass.getFieldTypes();
			final DbStringBuffer fieldValues = objectData.fieldValues;
			if (types.length != fieldValues.getSize()) {
				throw new RuntimeException("Incomplete objects image:" + objectData.oid);
			}
			final DbObject obj = db.reflectDbObject(dbClass, new DbOid(objectData.oid), DbConstants.ZERO_INT_ARRAY);
			final int dataIndex = obj.dataIndex;
			final int len = types.length;
			MappingPointer p = new MappingPointer(0);
			byte[] image;
			for (int i = 0; i < len; i++) {
				final byte type = types[i];
				final Object values = dbClass.data.fieldsValues[i];
				final String fieldValue = fieldValues.data[i];
				switch (type) {
					case DbType.BOOLEAN:
						((boolean[]) values)[dataIndex] = "true".equals(fieldValue);
						break;
					case DbType.BYTE:
						((byte[]) values)[dataIndex] = Byte.parseByte(fieldValue);
						break;
					case DbType.CHAR:
						((char[]) values)[dataIndex] = (char) Integer.parseInt(fieldValue);
						break;
					case DbType.DOUBLE:
						((double[]) values)[dataIndex] = Double.parseDouble(fieldValue);
						break;
					case DbType.FLOAT:
						((float[]) values)[dataIndex] = Float.parseFloat(fieldValue);
						break;
					case DbType.INT:
						((int[]) values)[dataIndex] = Integer.parseInt(fieldValue);
						break;
					case DbType.LONG:
						((long[]) values)[dataIndex] = Long.parseLong(fieldValue);
						break;
					case DbType.SHORT:
						((short[]) values)[dataIndex] = Short.parseShort(fieldValue);
						break;
					case DbType.REFERENCE:
						if ("null".equals(fieldValue)) {
							((int[]) ((Object[]) values)[0])[dataIndex] = -1;
							((long[]) ((Object[]) values)[1])[dataIndex] = 0;
						} else {
							DbOid refId = new DbOid(fieldValue);
							((int[]) ((Object[]) values)[0])[dataIndex] = refId.indexId;
							((long[]) ((Object[]) values)[1])[dataIndex] = refId.versionId;
						}
						break;
					case DbType.STRING:
						p.pos = 0;
						image = fieldValue.getBytes();
						((String[]) values)[dataIndex] = readString(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.ARRAY_OF_BYTE:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readByteArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.ARRAY_OF_CHAR:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readCharArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.ARRAY_OF_INT:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readIntArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.ARRAY_OF_STRING:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readStringArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.LINKED_LIST:
					case DbType.ARRAY_LIST:
					case DbType.INT_KEY_MAP:
					case DbType.REFERENCE_SET:
						/**
						 * Collection instance will be instantiated latter, after all objects will loaded
						 * before this it slot will be filled with it's image
						 */
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readIntArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.STRING_KEY_MAP:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readStringKeyMap(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					case DbType.STRING_SET:
					case DbType.STRING_MAP:
						p.pos = 0;
						image = fieldValue.getBytes();
						((Object[]) values)[dataIndex] = readStringArray(Base64.decode(fieldValue.getBytes(), 0, image.length), p);
						break;
					default:
						throw new RuntimeException("not valid type:" + type);
				}
			}
		}
	}

	private static class XMLClassData {
		String name;
		DbByteBuffer types = new DbByteBuffer();
		DbStringBuffer names = new DbStringBuffer();
		String oid;

		public void clear() {
			oid = null;
			name = null;
			types.clear();
			names.clear();
		}
	}

	private static class XMLObjectData {
		public String oid;
		public String className;
		public DbStringBuffer fieldValues = new DbStringBuffer();

		public void clear() {
			oid = null;
			fieldValues.clear();
			className = null;
		}
	}
}