package net.sourceforge.pain.db;
import java.io.*;
import java.util.*;
/**
* Superclass for any persistent PAiNDb object.<br>
* Subclass should implement provideSchema method.
*/
public abstract class DbObject {
int dataIndex;
int[] pageNums;
int indexId;
long versionId;
DbClassImpl dbClass;
/** next object in class extent*/
DbObject next;
/** prev object in class extent*/
DbObject prev;
DbObjectTransContext transContext;
int globalState = DbConstants.STATE_OBJ_DETACHED;
DbInverseRef inverseRef; // all collections entries with this object
/**
* This constuctor used for database startup time objects instantiation<br>
* Subclasses should also provide default empty constructor<br>
* (and not use any get/set methods in it)
*/
protected DbObject() {
}
/**
* this contructor used by user in runtime to create new objects<br>
* if no class instance for this object exists <code>provideSchema</code> method<br>
* will be invoked and DbClass instance created.
* Object is NEW after creation inside transaction or DIRTY in plain write mode.
* @param db
* @throws RuntimeException (TODO: will be changed to some PAIN RuntimeExceptionImpl)
*/
protected DbObject(final PainDB db) throws RuntimeException {
db.registerObject(this);
}
/**
* Every time we add new DbObject of the unknown class to database
* this method is called. DbClass instance will be automatically created.
* It's called only once for every new class.
*/
protected abstract DbClassSchema provideSchema();
private Object getFieldData(final int fid) {
ensureReal();
return dbClass.data.fieldsValues[fid];
}
public final boolean getBoolean(final int fid) {
return ((boolean[]) getFieldData(fid))[dataIndex];
}
public final byte getByte(final int fid) {
return ((byte[]) getFieldData(fid))[dataIndex];
}
public final char getChar(final int fid) {
return ((char[]) getFieldData(fid))[dataIndex];
}
public final double getDouble(final int fid) {
return ((double[]) getFieldData(fid))[dataIndex];
}
public final float getFloat(final int fid) {
return ((float[]) getFieldData(fid))[dataIndex];
}
public final int getInt(final int fid) {
return ((int[]) getFieldData(fid))[dataIndex];
}
public final long getLong(final int fid) {
return ((long[]) getFieldData(fid))[dataIndex];
}
public final short getShort(final int fid) {
return ((short[]) getFieldData(fid))[dataIndex];
}
public final DbObject getReference(final int fid) {
ensureType(fid, DbType.REFERENCE);
final int indexId = ((int[]) ((Object[]) getFieldData(fid))[0])[dataIndex];
if (indexId == -1) {
return null;
}
final DbObject result = dbClass._getDB().getObjectByIndexId(indexId);
if (result == null || result.versionId != ((long[]) ((Object[]) getFieldData(fid))[1])[dataIndex]) {
//lazy link checking
((int[]) ((Object[]) getFieldData(fid))[0])[dataIndex] = -1;
((long[]) ((Object[]) getFieldData(fid))[1])[dataIndex] = -1;
return null;
}
if (result.isDeleted()) {
return null;
}
return result;
}
public final String getString(final int fid) {
ensureType(fid, DbType.STRING); // we should check nullabla values for type!
return ((String[]) getFieldData(fid))[dataIndex];
}
/**
* NOTE: PainDB is not able to track any changes in arrays,<br>
* user should use getXArrayForRead to avoid copying.
* @param fid
* @return copy of the field value
*/
public final byte[] getByteArray(final int fid) {
ensureType(fid, DbType.ARRAY_OF_BYTE);
final byte[] data = (byte[]) ((Object[]) getFieldData(fid))[dataIndex];
if (data == null) {
return null;
}
return (byte[]) data.clone();
}
/**
* NOTE: PainDB is not able to track any changes in arrays, this method provides direct access to arrays
* user should not modify array instance obtained with this method (rollback info could be lost)
* @param fid
* @return original field value(array, do not make copy)
*/
public final byte[] getByteArrayForRead(final int fid) {
ensureType(fid, DbType.ARRAY_OF_BYTE);
return (byte[]) ((Object[]) getFieldData(fid))[dataIndex];
}
/**
* NOTE: PainDB is not able to track any changes in arrays,<br>
* user should use getXArrayForRead to avoid copying.
* @param fid
* @return copy of the field value
*/
public final char[] getCharArray(final int fid) {
ensureType(fid, DbType.ARRAY_OF_CHAR);
final char[] data = (char[]) ((Object[]) getFieldData(fid))[dataIndex];
if (data == null) {
return null;
}
return (char[]) data.clone();
}
/**
* NOTE: PainDB is not able to track any changes in arrays, this method provides direct access to arrays
* user should not modify array instance obtained with this method (rollback info could be lost)
* @param fid
* @return original field value(array, do not make copy)
*/
public final char[] getCharArrayForRead(final int fid) {
ensureType(fid, DbType.ARRAY_OF_CHAR);
return (char[]) ((Object[]) getFieldData(fid))[dataIndex];
}
/**
* NOTE: PainDB is not able to track any changes in arrays,<br>
* user should use getXArrayForRead to avoid copying.
* @param fid
* @return copy of the field value
*/
public final int[] getIntArray(final int fid) {
ensureType(fid, DbType.ARRAY_OF_INT);
final int[] data = (int[]) ((Object[]) getFieldData(fid))[dataIndex];
if (data == null) {
return null;
}
return (int[]) data.clone();
}
/**
* NOTE: PainDB is not able to track any changes in arrays, this method provides direct access to arrays
* user should not modify array instance obtained with this method (rollback info could be lost)
* @param fid
* @return original field value(array, do not make copy)
*/
public final int[] getIntArrayForRead(final int fid) {
ensureType(fid, DbType.ARRAY_OF_INT);
return (int[]) ((Object[]) getFieldData(fid))[dataIndex];
}
/**
* NOTE: PainDB is not able to track any changes in arrays, this method provides direct access to arrays
* user should not modify array instance obtained with this method (rollback info could be lost)
* @param fid
* @return original field value(array, do not make copy)
*/
public final String[] getStringArray(final int fid) {
ensureType(fid, DbType.ARRAY_OF_STRING);
final String[] data = (String[]) ((Object[]) getFieldData(fid))[dataIndex];
if (data == null) {
return null;
}
return (String[]) data.clone();
}
/**
* NOTE: PainDB is not able to track any changes in arrays, this method provides direct access to arrays
* user should not modify array instance obtained with this method (rollback info could be lost)
* @param fid
* @return original field value(array, do not make copy)
*/
public final String[] getStringArrayForRead(final int fid) {
ensureType(fid, DbType.ARRAY_OF_STRING);
return (String[]) ((Object[]) getFieldData(fid))[dataIndex];
}
public final List getList(final int fid) {
return (dbClass.fieldTypes[fid] == DbType.LINKED_LIST) ? (List) getLinkedList(fid) : (List) getArrayList(fid);
}
/**
* Note: there is only getter method for Lists. List could be empty but not null!
* @param fid number of field
* @return list
*/
public final DbLinkedList getLinkedList(final int fid) {
ensureType(fid, DbType.LINKED_LIST);
DbLinkedList result = (DbLinkedList) ((Object[]) getFieldData(fid))[dataIndex];
if (result == null) { // lazy instantiation
result = (DbLinkedList) (((Object[]) getFieldData(fid))[dataIndex] = new DbLinkedList(this, fid));
}
return result;
}
/**
* Note: there is only getter method for Lists. List could be empty but not null!
* @param fid number of field
* @return list
*/
public final DbArrayList getArrayList(final int fid) {
ensureType(fid, DbType.ARRAY_LIST);
DbArrayList result = (DbArrayList) ((Object[]) getFieldData(fid))[dataIndex];
if (result == null) { // lazy instantiation
result = (DbArrayList) (((Object[]) getFieldData(fid))[dataIndex] = new DbArrayList(this, fid));
}
return result;
}
/**
* Note: there is only getter method for Maps. Map could be empty but never null!
* @param fid number of field
* @return map
*/
public final DbIntKeyMap getIntKeyMap(final int fid) {
ensureType(fid, DbType.INT_KEY_MAP);
DbIntKeyMap result = (DbIntKeyMap) ((Object[]) getFieldData(fid))[dataIndex];
if (result == null) { // lazy instantiation
result = (DbIntKeyMap) (((Object[]) getFieldData(fid))[dataIndex] = new DbIntKeyMap(this, fid));
}
return result;
}
/**
* Note: there is only getter method for Maps. Map could be empty but never null!
* @param fid number of field
* @return map
*/
public final DbStringKeyMap getStringKeyMap(final int fid) {
ensureType(fid, DbType.STRING_KEY_MAP);
DbStringKeyMap result = (DbStringKeyMap) ((Object[]) getFieldData(fid))[dataIndex];
if (result == null) { // lazy instantiation
result = (DbStringKeyMap) (((Object[]) getFieldData(fid))[dataIndex] = new DbStringKeyMap(this, fid));
}
return result;
}
/**
* Note: there is only getter method for Sets. Set could be empty but never null!
* @param fid
* @return set
*/
public final DbReferenceSet getRefSet(final int fid) {
ensureType(fid, DbType.REFERENCE_SET);
DbReferenceSet result = (DbReferenceSet) ((Object[]) getFieldData(fid))[dataIndex];
if (result == null) { // lazy instantiation
result = (DbReferenceSet) (((Object[]) getFieldData(fid))[dataIndex] = new DbReferenceSet(this, fid));
}
return result;
}
/* Note: there is only getter method for Sets. Set could be empty but never null!
* @param fid
* @return set
*/
public final DbStringSet getStringSet(final int fid) {
ensureType(fid, DbType.STRING_SET);
final DbStringSet result;
final Object image = ((Object[]) getFieldData(fid))[dataIndex];
if (image == null) { // lazy instantiation
result = (DbStringSet) (((Object[]) getFieldData(fid))[dataIndex] = new DbStringSet(this, fid));
} else if (!(image instanceof DbStringSet)) { // lazy instantiation of not empty StringSet
result = (DbStringSet) (((Object[]) getFieldData(fid))[dataIndex] = new DbStringSet(this, (String[]) image, fid));
} else {
result = (DbStringSet) image;
}
return result;
}
/* Note: there is only getter method for Maps. Map could be empty but never null!
* @param fid
* @return set
*/
public final DbStringMap getStringMap(final int fid) {
ensureType(fid, DbType.STRING_MAP);
final DbStringMap result;
final Object image = ((Object[]) getFieldData(fid))[dataIndex];
if (image == null) { // lazy instantiation
result = (DbStringMap) (((Object[]) getFieldData(fid))[dataIndex] = new DbStringMap(this, fid));
} else if (!(image instanceof DbStringMap)) { // lazy instantiation of not empty StringMap
result = (DbStringMap) (((Object[]) getFieldData(fid))[dataIndex] = new DbStringMap(this, (String[]) image, fid));
} else {
result = (DbStringMap) image;
}
return result;
}
final void onCollectionChange(final DbCollection collection) {
onChange();
if (transContext != null) {
dbClass.data.backupCollection(collection); //delayed backup
}
}
private void onArrayChange(final int fid) {
onChange();
if (transContext != null) {
dbClass.data.backupArray(this, fid); //delayed backup
}
}
private void onChange() {
ensureReal();
final PainDB db = dbClass._getDB();
if (transContext == null || transContext.trans != db.activeTrans) {
db.markDirty(this); //will add in trans context and mark state as dirty
}
}
public final void setBoolean(final int fid, final boolean value) {
ensureType(fid, DbType.BOOLEAN);
onChange();
((boolean[]) getFieldData(fid))[dataIndex] = value;
}
public final void setByte(final int fid, final byte value) {
ensureType(fid, DbType.BYTE);
onChange();
((byte[]) getFieldData(fid))[dataIndex] = value;
}
public final void setChar(final int fid, final char value) {
ensureType(fid, DbType.CHAR);
onChange();
((char[]) getFieldData(fid))[dataIndex] = value;
}
public final void setDouble(final int fid, final double value) {
ensureType(fid, DbType.DOUBLE);
onChange();
((double[]) getFieldData(fid))[dataIndex] = value;
}
public final void setFloat(final int fid, final float value) {
ensureType(fid, DbType.FLOAT);
onChange();
((float[]) getFieldData(fid))[dataIndex] = value;
}
public final void setInt(final int fid, final int value) {
ensureType(fid, DbType.INT);
onChange();
((int[]) getFieldData(fid))[dataIndex] = value;
}
public final void setLong(final int fid, final long value) {
ensureType(fid, DbType.LONG);
onChange();
((long[]) getFieldData(fid))[dataIndex] = value;
}
public final void setShort(final int fid, final short value) {
ensureType(fid, DbType.SHORT);
onChange();
((short[]) getFieldData(fid))[dataIndex] = value;
}
public final void setString(final int fid, final String value) {
ensureType(fid, DbType.STRING);
onChange();
((String[]) getFieldData(fid))[dataIndex] = value;
}
public final void setReference(final int fid, final DbObject value) {
ensureType(fid, DbType.REFERENCE);
onChange();
if (value == null) {
((int[]) ((Object[]) getFieldData(fid))[0])[dataIndex] = -1;
((long[]) ((Object[]) getFieldData(fid))[1])[dataIndex] = -1;
} else {
getDB().ensureOwner(value);
value.ensureReal();
((int[]) ((Object[]) getFieldData(fid))[0])[dataIndex] = value.indexId;
((long[]) ((Object[]) getFieldData(fid))[1])[dataIndex] = value.versionId;
}
}
private int _getState() {
return transContext == null ? globalState : transContext.state;
}
public final void setByteArray(final int fid, final byte[] value) {
ensureType(fid, DbType.ARRAY_OF_BYTE);
onArrayChange(fid);
//todo: clone????
((Object[]) getFieldData(fid))[dataIndex] = value;
}
public final void setCharArray(final int fid, final char[] value) {
ensureType(fid, DbType.ARRAY_OF_CHAR);
onArrayChange(fid);
((Object[]) getFieldData(fid))[dataIndex] = value;
}
public final void setStringArray(final int fid, final String[] value) {
ensureType(fid, DbType.ARRAY_OF_STRING);
onArrayChange(fid);
((Object[]) getFieldData(fid))[dataIndex] = value;
}
public final void setIntArray(final int fid, final int[] value) {
ensureType(fid, DbType.ARRAY_OF_INT);
onArrayChange(fid);
((Object[]) getFieldData(fid))[dataIndex] = value;
}
/**
* removes object from database.
* Object could be restored during rollback call.
*/
public void delete() {
onChange();//will be added in trans context
//I think it was bad design, subclass should override this method to track it calls now:
// try {
// onDelete();
// } catch (Exception e) {
// throw new RuntimeException(e);
// } finally {
dbClass._getDB().markDeleted(this);
// }
}
private void ensureType(final int fid, final int type) {
if (dbClass.fieldTypes[fid] != type) {
throw new RuntimeException("Invalid field type: Class:" + getClass().getName() + " field:" + dbClass.fieldNames[fid] + " type passed:" + type + " real type:" + dbClass.fieldTypes[fid]);
}
}
final void ensureReal() {
if (_getState() < DbConstants.STATE_OBJ_NEW) {
throw new RuntimeException("Illegal object state: " + DbConstants.OBJ_STATE_NAMES[_getState()]);
}
dbClass.checkDbState();
}
public final PainDB getDB() {
ensureReal();
return _getDB();
}
final PainDB _getDB() {
return dbClass._getDB();
}
public final DbClass getDbClass() {
return dbClass;
}
/**
* @return serializable object id. This id is unique for object and will never be reused
*/
public final Serializable getOid() {
ensureReal();
return new DbOid(indexId, versionId);
}
final void ensureDb(final DbObject obj) {
_getDB().ensureOwner(obj);
}
/**
* @return true if obj was deleted in current transaction, commit still is not called
*/
public final boolean isDeleted() {
return dbClass != null && !dbClass.isDetached() && _getState() == DbConstants.STATE_OBJ_DELETED;
}
/**
* @return true if obj was modified (or created in plainwrite mode) and still not flushed
*/
public final boolean isDirty() {
return dbClass != null && !dbClass.isDetached() && _getState() == DbConstants.STATE_OBJ_DIRTY;
}
/**
* @return true if obj was created in current transaction
*/
public final boolean isNew() {
return dbClass != null && !dbClass.isDetached() && _getState() == DbConstants.STATE_OBJ_NEW;
}
public final boolean isClean() {
return !dbClass.isDetached() && _getState() == DbConstants.STATE_OBJ_CLEAN;
}
/**
* @return true if object is not registered in db, was deleted and commited or deleted in plainwrite mode
* or database was closed
*/
public final boolean isDetached() {
final boolean detached = dbClass == null || dbClass.isDetached() || _getState() == DbConstants.STATE_OBJ_DETACHED;
if (detached) {
assert(dbClass == null || dbClass.isDetached());
}
return detached;
}
}