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