package net.sourceforge.pain.db; import net.sourceforge.pain.util.*; import java.lang.reflect.*; import java.util.*; /** * User: fmike Date: 18.03.2003 Time: 14:00:19 */ abstract class DbClassImpl implements DbClass { private static final int DEFAULT_VALUES_CAPACITY = 128; private static final float VALUES_CAPACITY_EXT_K = 1.2F; private final PainDB db; final byte[] fieldTypes; final String[] fieldNames; private final String className; DbObject firstInExtent; private DbObject lastInExtent; private int nObjects = 0; int modCount = 0; // for extent iterator private final boolean hasCollections; // initialize collections mainly after allobjects instantiated private boolean dbClosed; final ClassData data; DbClassTransContext transContext = null; DbClassImpl(final PainDB db, final byte[] fieldTypes, final String[] fieldNames, final String className) { this.db = db; this.fieldTypes = fieldTypes; this.fieldNames = fieldNames; this.className = className; hasCollections = hasCollections(); dbClosed = false; data = new ClassData(); } private boolean hasCollections() { for (int i = 0; i < fieldTypes.length; i++) { final int type = fieldTypes[i]; if (type > 19) { return true; } } return false; } public final PainDB getDB() { checkDbState(); return db; } final PainDB _getDB() { return db; } public final int getNumberOfFields() { return fieldNames.length; } final byte[] getFieldTypes() { return fieldTypes; } final String[] getFieldNames() { return fieldNames; } public final String getClassName() { return className; } public final String getFieldName(final int n) { return getFieldNames()[n]; } public final byte getFieldType(final int n) { return getFieldTypes()[n]; } public final Iterator extentIterator() { return new DbExtentIterator(this); } public final int getNumberOfObjects() { return nObjects; } abstract Constructor getReadConstructor(); final void addToExtent(final DbObject obj) { if (lastInExtent != null) { lastInExtent.next = obj; obj.prev = lastInExtent; lastInExtent = obj; } else { firstInExtent = lastInExtent = obj; } nObjects++; modCount++; } private void removeFromExtent(final DbObject obj) { if (obj == firstInExtent) { firstInExtent = obj.next; } if (obj == lastInExtent) { lastInExtent = obj.prev; } if (obj.prev != null) { obj.prev.next = obj.next; } if (obj.next != null) { obj.next.prev = obj.prev; } obj.prev = null; obj.next = null; nObjects--; modCount++; } /** * do not touches backupData */ final void onMarkDeleted(final DbObject obj) { removeFromExtent(obj); if (hasCollections) { // collections has references to other objects, this references registered as inverse, // we should release it if (transContext != null && transContext.backupData != null && obj.transContext.backupDataIndex != -1) { //if not new class and not new obj /** if some collections was not backuped we should backup it now */ transContext.backupData.ensureCollectionsBackup(data, obj.dataIndex, obj.transContext.backupDataIndex); } freeCollections(obj);// free all references to other objects from SCO collections fields of deleted object } } private void freeCollections(final DbObject obj) { final int dataIndex = obj.dataIndex; for (int i = 0; i < fieldTypes.length; i++) { switch (fieldTypes[i]) { //++deallocateDataIndex method will null all this references during owner detach case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.STRING_KEY_MAP: case DbType.REFERENCE_SET: // case DbType.STRING_SET: has no refs to other objects final DbCollection c = ((DbCollection) ((Object[]) data.fieldsValues[i])[dataIndex]); if (c != null) { c._clear(); } break; } } } /** startup method */ final void onDbLoaded() { if (hasCollections) { int[] image; for (DbObject runner = firstInExtent; runner != null; runner = runner.next) { final int dataIndex = runner.dataIndex; for (int i = 0; i < fieldTypes.length; i++) { switch (fieldTypes[i]) { case DbType.LINKED_LIST: // changing slot value from list image to DbLinkedList instance image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex]; ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbLinkedList(runner, image, i);//lazy instantiation if null(empty) break; case DbType.ARRAY_LIST: // changing slot value from list image to DbArrayList instance image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex]; ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbArrayList(runner, image, i);//lazy instantiation if null(empty) break; case DbType.INT_KEY_MAP: // changing slot value from map image to DbIntKeyMap instance image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex]; ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbIntKeyMap(runner, image, i);//lazy instantiation if null(empty) break; case DbType.STRING_KEY_MAP: // changing slot value from map image to DbIntKeyMap instance Object[] skmImage = (Object[]) ((Object[]) data.fieldsValues[i])[dataIndex]; ((Object[]) data.fieldsValues[i])[dataIndex] = (skmImage == null || skmImage.length == 0) ? null : new DbStringKeyMap(runner, (String[]) skmImage[0], (int[]) skmImage[1], i);//lazy instantiation if null(empty) break; case DbType.REFERENCE_SET: // changing slot value from set image to DbReferenceSet instance image = (int[]) ((Object[]) data.fieldsValues[i])[dataIndex]; ((Object[]) data.fieldsValues[i])[dataIndex] = (image == null || image.length == 0) ? null : new DbReferenceSet(runner, image, i);//lazy instantiation if null(empty) break; case DbType.STRING_SET: // lazy instantiation break; } } } } } final void setDbClosed() { dbClosed = true; } final void checkDbState() { if (dbClosed) { throw new RuntimeException("database was closed!"); } } public final ClassData createClassData() { return new ClassData(); } /** * here we are sure that obj and class has the same trans context * @param obj * @return */ final int backupObject(final DbObject obj) { PAssert.that(obj.dbClass == this); final ClassData backup = transContext.backupData; final int toIndex = backup.allocateDataIndex(); return data.backupTo(obj.dataIndex, backup, toIndex); } public final int moveBackupData(final DbObject obj, final int backupDataIndex, final ClassData backupFrom, final ClassData backupTo) { PAssert.that(obj.dbClass == this); final int toIndex = backupTo.allocateDataIndex(); backupFrom.moveBackupData(backupDataIndex, backupTo, toIndex); return toIndex; } /** * copy all values from current classtranscontext.backupData to data, used on rollback only * @param obj */ public final void restoreObject(final DbObject obj) { PAssert.that(obj.dbClass == this); data.restoreFromBackup(obj.dataIndex, transContext.backupData, obj.transContext.backupDataIndex); } public boolean isDetached() { return dbClosed; } final class ClassData { final Object[] fieldsValues; private final DbIntBuffer freeDataIds = new DbIntBuffer(); private int valuesCapacity; public ClassData() { fieldsValues = new Object[getNumberOfFields()]; valuesCapacity = 0; extendValuesCapacity(DEFAULT_VALUES_CAPACITY); } private void extendValuesCapacity(final int newValuesCapacity) { for (int i = 0; i < fieldsValues.length; i++) { final Object container; if (fieldTypes[i] == DbType.REFERENCE) { // reference presented by two values: int(indexId) and long(versionId) final Object[] arr; final Object[] oldArr = (Object[]) fieldsValues[i]; container = arr = new Object[2]; arr[0] = new int[newValuesCapacity]; arr[1] = new long[newValuesCapacity]; if (valuesCapacity != 0) { System.arraycopy(oldArr[0], 0, arr[0], 0, valuesCapacity); System.arraycopy(oldArr[1], 0, arr[1], 0, valuesCapacity); } } else { switch (fieldTypes[i]) { case DbType.BOOLEAN: container = new boolean[newValuesCapacity]; break; case DbType.BYTE: container = new byte[newValuesCapacity]; break; case DbType.CHAR: container = new char[newValuesCapacity]; break; case DbType.DOUBLE: container = new double[newValuesCapacity]; break; case DbType.FLOAT: container = new float[newValuesCapacity]; break; case DbType.INT: container = new int[newValuesCapacity]; break; case DbType.LONG: container = new long[newValuesCapacity]; break; case DbType.SHORT: container = new short[newValuesCapacity]; break; case DbType.STRING: container = new String[newValuesCapacity]; break; case DbType.ARRAY_OF_BYTE: case DbType.ARRAY_OF_CHAR: case DbType.ARRAY_OF_INT: case DbType.ARRAY_OF_STRING: case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.STRING_KEY_MAP: case DbType.REFERENCE_SET: case DbType.STRING_SET: container = new Object[newValuesCapacity]; break; default: throw new RuntimeException("not valid type:" + fieldTypes[i]); } if (valuesCapacity != 0) { System.arraycopy(fieldsValues[i], 0, container, 0, valuesCapacity); } } fieldsValues[i] = container; } freeDataIds.ensureCapacity(newValuesCapacity); for (int i = newValuesCapacity - 1; --i >= valuesCapacity;) { freeDataIds.add(i); } valuesCapacity = newValuesCapacity; } final synchronized int allocateDataIndex() { if (freeDataIds.getSize() == 0) { extendValuesCapacity((int) (valuesCapacity * VALUES_CAPACITY_EXT_K)); } return freeDataIds.removeLast(); } void deallocateDataIndex(final int index) { freeDataIds.add(index); //+ free some resources, we should free primitives-> this slot will be reused-> should set default values for new obj for (int i = 0; i < fieldsValues.length; i++) { switch (fieldTypes[i]) { case DbType.BOOLEAN: ((boolean[]) fieldsValues[i])[index] = false; break; case DbType.BYTE: ((byte[]) fieldsValues[i])[index] = 0; break; case DbType.CHAR: ((char[]) fieldsValues[i])[index] = 0; break; case DbType.DOUBLE: ((double[]) fieldsValues[i])[index] = 0; break; case DbType.FLOAT: ((float[]) fieldsValues[i])[index] = 0; break; case DbType.INT: ((int[]) fieldsValues[i])[index] = 0; break; case DbType.LONG: ((long[]) fieldsValues[i])[index] = 0; break; case DbType.SHORT: ((short[]) fieldsValues[i])[index] = 0; break; case DbType.STRING: ((String[]) fieldsValues[i])[index] = null; break; case DbType.REFERENCE: ((int[]) ((Object[]) fieldsValues[i])[0])[index] = -1; ((long[]) ((Object[]) fieldsValues[i])[1])[index] = -1; break; case DbType.ARRAY_OF_BYTE: case DbType.ARRAY_OF_CHAR: case DbType.ARRAY_OF_INT: case DbType.ARRAY_OF_STRING: case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.REFERENCE_SET: case DbType.STRING_KEY_MAP: case DbType.STRING_SET: ((Object[]) fieldsValues[i])[index] = null; break; } } } /** called only for backups during flush or after rollbacks*/ void clear() { PAssert.that(this != data); // nothing to to really before we will use cache for classData. GC will do it's work for (int i = 0; i < fieldsValues.length; i++) { fieldsValues[i] = null; } } public void moveBackupData(final int fromIndex, final ClassData backupTo, final int toIndex) { PAssert.that(this != data && backupTo != data);//remove after tests final Object[] toValues = backupTo.fieldsValues; final Object[] fromValues = this.fieldsValues; for (int i = 0; i < fieldsValues.length; i++) { switch (fieldTypes[i]) { case DbType.BOOLEAN: ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[fromIndex]; break; case DbType.BYTE: ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[fromIndex]; break; case DbType.CHAR: ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[fromIndex]; break; case DbType.DOUBLE: ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[fromIndex]; break; case DbType.FLOAT: ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[fromIndex]; break; case DbType.INT: ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[fromIndex]; break; case DbType.LONG: ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[fromIndex]; break; case DbType.SHORT: ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[fromIndex]; break; case DbType.STRING: ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[fromIndex]; break; case DbType.REFERENCE: ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[fromIndex]; ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[fromIndex]; break; case DbType.ARRAY_OF_BYTE: case DbType.ARRAY_OF_CHAR: case DbType.ARRAY_OF_INT: case DbType.ARRAY_OF_STRING: case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.STRING_KEY_MAP: case DbType.REFERENCE_SET: case DbType.STRING_SET: ((Object[]) toValues[i])[toIndex] = ((Object[]) fromValues[i])[fromIndex]; break; } } } public int backupTo(final int fromIndex, final ClassData backupData, final int toIndex) { PAssert.that(this == data); // this operation used only to save data from DbClassImpl.data transaction backupData final Object[] toValues = backupData.fieldsValues; final Object[] fromValues = this.fieldsValues; for (int i = 0; i < fieldsValues.length; i++) { switch (fieldTypes[i]) { case DbType.BOOLEAN: ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[fromIndex]; break; case DbType.BYTE: ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[fromIndex]; break; case DbType.CHAR: ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[fromIndex]; break; case DbType.DOUBLE: ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[fromIndex]; break; case DbType.FLOAT: ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[fromIndex]; break; case DbType.INT: ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[fromIndex]; break; case DbType.LONG: ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[fromIndex]; break; case DbType.SHORT: ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[fromIndex]; break; case DbType.STRING: ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[fromIndex]; break; case DbType.REFERENCE: ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[fromIndex]; ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[fromIndex]; break; //collections use lazy backup (backupCollection method) // arrays use lazy backup to } } return toIndex; } public void restoreFromBackup(final int toIndex, final ClassData backupData, final int backupIndex) { PAssert.that(this == data); // only data could be restored from backups during rollbacks final Object[] toValues = fieldsValues; final Object[] fromValues = backupData.fieldsValues; for (int i = 0; i < fieldsValues.length; i++) { switch (fieldTypes[i]) { case DbType.BOOLEAN: ((boolean[]) toValues[i])[toIndex] = ((boolean[]) fromValues[i])[backupIndex]; break; case DbType.BYTE: ((byte[]) toValues[i])[toIndex] = ((byte[]) fromValues[i])[backupIndex]; break; case DbType.CHAR: ((char[]) toValues[i])[toIndex] = ((char[]) fromValues[i])[backupIndex]; break; case DbType.DOUBLE: ((double[]) toValues[i])[toIndex] = ((double[]) fromValues[i])[backupIndex]; break; case DbType.FLOAT: ((float[]) toValues[i])[toIndex] = ((float[]) fromValues[i])[backupIndex]; break; case DbType.INT: ((int[]) toValues[i])[toIndex] = ((int[]) fromValues[i])[backupIndex]; break; case DbType.LONG: ((long[]) toValues[i])[toIndex] = ((long[]) fromValues[i])[backupIndex]; break; case DbType.SHORT: ((short[]) toValues[i])[toIndex] = ((short[]) fromValues[i])[backupIndex]; break; case DbType.STRING: ((String[]) toValues[i])[toIndex] = ((String[]) fromValues[i])[backupIndex]; break; case DbType.REFERENCE: ((int[]) ((Object[]) toValues[i])[0])[toIndex] = ((int[]) ((Object[]) fromValues[i])[0])[backupIndex]; ((long[]) ((Object[]) toValues[i])[1])[toIndex] = ((long[]) ((Object[]) fromValues[i])[1])[backupIndex]; break; case DbType.ARRAY_OF_BYTE: case DbType.ARRAY_OF_CHAR: case DbType.ARRAY_OF_INT: case DbType.ARRAY_OF_STRING: final Object backupArr = ((Object[]) fromValues[i])[backupIndex]; if (backupArr != null) { // null means that array was not modified if (backupArr == DbConstants.ZERO_INT_ARRAY) { // zero_int_array is used to backup null values ((Object[]) toValues[i])[toIndex] = null; } else { ((Object[]) toValues[i])[toIndex] = backupArr; } } break; case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.STRING_KEY_MAP: case DbType.REFERENCE_SET: final Object backup = ((Object[]) fromValues[i])[backupIndex]; if (backup != null) {//backup == null means there was no backup (no collection change) final DbCollection c = (DbCollection) ((Object[]) toValues[i])[toIndex]; c.restoreFromBackup(backup); } break; case DbType.STRING_SET: final Object cbackup = ((Object[]) fromValues[i])[backupIndex]; final Object dest = ((Object[]) toValues[i])[toIndex]; if (dest instanceof DbStringSet) { ((DbStringSet) dest).restoreFromBackup(cbackup); } else { ((Object[]) toValues[i])[toIndex] = cbackup; //was not instantiated (backuped if obj deleted) } } } } /**collections use lazy backup (backup on change) * @param collection */ void backupCollection(final DbCollection collection) { PAssert.that(this == data); final int toIndex = collection.owner.transContext.backupDataIndex; // backup index final int fid = collection.fid; if (toIndex == -1) { return; // new object } final Object[] toValues = transContext.backupData.fieldsValues; if (((Object[]) toValues[fid])[toIndex] != null) { // backup already done return; } // final Object[] fromValues = this.fieldsValues; // final int fromIndex = collection.owner.dataIndex; // final DbCollection c = (DbCollection) ((Object[]) fromValues[fid])[fromIndex]; ((Object[]) toValues[fid])[toIndex] = collection.createBackupImage(); } /**arrays use lazy backup (backup on change) */ void backupArray(final DbObject obj, final int fid) { PAssert.that(this == data); final int toIndex = obj.transContext.backupDataIndex; if (toIndex == -1) {// new object return; } final int fromIndex = obj.dataIndex; final Object[] toValues = transContext.backupData.fieldsValues; if (((Object[]) toValues[fid])[toIndex] != null) { return; // already backed up } final Object[] fromValues = this.fieldsValues; final Object data = ((Object[]) fromValues[fid])[fromIndex]; final Object backupData = data == null ? DbConstants.ZERO_INT_ARRAY : data; ((Object[]) toValues[fid])[toIndex] = backupData; } public void ensureCollectionsBackup(ClassData fromData, int fromIndex, int toIndex) { for (int i = 0; i < fieldTypes.length; i++) { switch (fieldTypes[i]) { //++deallocateDataIndex method will null all this references during owner detach case DbType.LINKED_LIST: case DbType.ARRAY_LIST: case DbType.INT_KEY_MAP: case DbType.STRING_KEY_MAP: case DbType.REFERENCE_SET: if (((Object[]) fieldsValues[i])[toIndex] == null) { // if was not backuped final DbCollection fromCollection = ((DbCollection) ((Object[]) fromData.fieldsValues[i])[fromIndex]); if (fromCollection != null) { // null => zero capacity and was not instantiated ((Object[]) fieldsValues[i])[toIndex] = fromCollection.createBackupImage(); } } break; case DbType.STRING_SET: if (((Object[]) fieldsValues[i])[toIndex] == null) { // if was not backuped final Object o = ((Object[]) fromData.fieldsValues[i])[fromIndex]; if (o != null && o instanceof DbStringSet) { ((Object[]) fieldsValues[i])[toIndex] = ((DbStringSet) o).createBackupImage(); } else { //was not instantiated (String[] or null if zero capacity) ((Object[]) fieldsValues[i])[toIndex] = o; } } break; } } } }; }