package net.sourceforge.pain.db;
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;
private final WeakHashMap weakIteratorsByObject = new WeakHashMap(10);
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() {
final int len = fieldTypes.length;
for (int i = 0; i < len; 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(boolean weak) {
return new DbExtentIterator(this, weak);
}
public Iterator extentIterator() {
return extentIterator(false);
}
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;
}
if (!weakIteratorsByObject.isEmpty()) {
DbExtentIterator iterator = (DbExtentIterator) weakIteratorsByObject.get(obj);
if (iterator != null) {
iterator.onCurrentDelete();
}
}
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 collections fields of deleted object (remove Inverse references from objs in collection)
}
}
private void freeCollections(final DbObject obj) {
final int dataIndex = obj.dataIndex;
final int len = fieldTypes.length;
for (int i = 0; i < len; 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;
final int len = fieldTypes.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
// 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) {
assert(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) {
assert(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) {
assert(obj.dbClass == this);
if (obj.transContext.state == DbConstants.STATE_OBJ_DELETED) {
addToExtent(obj);
}
data.restoreFromBackup(obj.dataIndex, transContext.backupData, obj.transContext.backupDataIndex);
}
public boolean isDetached() {
return dbClosed;
}
void weakIteratorReassign(DbExtentIterator iterator, DbObject currentObj) {
if (currentObj == null) {
weakIteratorsByObject.values().remove(iterator);
} else {
weakIteratorsByObject.put(currentObj, iterator);
}
}
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) {
final int len = fieldsValues.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
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
final int len = fieldsValues.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
((Object[]) fieldsValues[i])[index] = null;
break;
}
}
}
/**
* called only for backups during flush or after rollbacks
*/
void clear() {
assert(this != data);
// nothing to to really before we will use cache for classData. GC will do it's work
final int len = fieldsValues.length;
for (int i = 0; i < len; i++) {
fieldsValues[i] = null;
}
}
public void moveBackupData(final int fromIndex, final ClassData backupTo, final int toIndex) {
assert(this != data && backupTo != data);//remove after tests
final Object[] toValues = backupTo.fieldsValues;
final Object[] fromValues = this.fieldsValues;
final int len = fieldsValues.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
((Object[]) toValues[i])[toIndex] = ((Object[]) fromValues[i])[fromIndex];
break;
}
}
}
public int backupTo(final int fromIndex, final ClassData backupData, final int toIndex) {
assert(this == data); // this operation used only to save data from DbClassImpl.data transaction backupData
final Object[] toValues = backupData.fieldsValues;
final Object[] fromValues = this.fieldsValues;
final int len = fieldsValues.length;
for (int i = 0; i < len; 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) {
assert(this == data); // only data could be restored from backups during rollbacks
final Object[] toValues = fieldsValues;
final Object[] fromValues = backupData.fieldsValues;
final int len = fieldsValues.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
final Object cbackup = ((Object[]) fromValues[i])[backupIndex];
if (cbackup != null) {//backup == null means there was no backup (no collection change)
final Object dest = ((Object[]) toValues[i])[toIndex];
if (dest instanceof DbAbstractStringMap) {
((DbAbstractStringMap) dest).restoreFromBackup(cbackup);
} else {
((Object[]) toValues[i])[toIndex] = cbackup; //was not instantiated (backuped if obj deleted)
}
}
break;
}
}
}
/**
* collections use lazy backup (backup on change)
*
* @param collection
*/
void backupCollection(final DbCollection collection) {
assert(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) {
assert(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) {
final int len = fieldTypes.length;
for (int i = 0; i < len; 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:
case DbType.STRING_MAP:
if (((Object[]) fieldsValues[i])[toIndex] == null) { // if was not backuped
final Object o = ((Object[]) fromData.fieldsValues[i])[fromIndex];
if (o != null && o instanceof DbAbstractStringMap) {
((Object[]) fieldsValues[i])[toIndex] = ((DbStringSet) o).createBackupImage();
} else { //was not instantiated (String[] or null if zero capacity)
((Object[]) fieldsValues[i])[toIndex] = o;
}
}
break;
}
}
}
};
}