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; } } }