/
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.*;


/**
 * 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];

	public 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 byte[] types = dbClass.getFieldTypes();
		final int dataIndex = dbClass.data.allocateDataIndex();
		final MappingPointer p = new MappingPointer(16);
		for (int i = 0; i < types.length; 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
					 * @param data
					 * @param p
					 * @return
					 */
					((Object[]) values)[dataIndex] = readIntArray(data, p);
					break;
				case DbType.STRING_KEY_MAP:
					((Object[]) values)[dataIndex] = readStringKeyMap(data, p);
					break;
				case DbType.STRING_SET:
					((Object[]) values)[dataIndex] = readStringArray(data, p);
					break;
				default:
					throw new RuntimeException("not valid type:" + type);
			}
		}
		return db.reflectDbObject(dbClass, oid, dataIndex, pageNums);
	}

	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);
		for (int i = 0; i < types.length; 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:
					Object o = ((Object[]) values)[dataIndex];
					if (o instanceof DbStringSet) {
						writeStringSet(objImageBuf, (DbStringSet) 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();
				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) {
		if (strings == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if (strings.length > 0) {
			buf.add(strings.length);
			for (int i = 0; i < strings.length; 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();
			for (int i = 0; i < data.length; 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 (set == null || (size = set._size()) == 0) { // lazy instantiation allows it
			writeStringArray(buf, ZERO_STRING_ARRAY);
		} else {
			buf.prepareToAdd(4 + 16 * size);
			buf.add(size); //4
			final DbStringSet.SetEntry data[] = set._getData();
			for (int i = 0; i < data.length; i++) {
				for (DbStringSet.SetEntry e = data[i]; e != null; e = e.next) {
					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();
			for (int i = 0; i < data.length; 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 < data.length; 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();
			for (int i = 0; i < data.length; 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) {
		if (s == null) {
			buf.add(0);
			writeBoolean(buf, true);
		} else if (s.length() > 0) {
			buf.add(s.length());
			if (charBuf.length < s.length()) {
				charBuf = new char[s.length()];
			}
			s.getChars(0, s.length(), charBuf, 0);
			buf.add(charBuf, 0, s.length());
		} 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);
			PAssert.that((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);
	}

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

	/**
	 *
	 * @param data
	 * @return  true if this page is object start page and not class start page
	 */
	public 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;

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