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