/
area/
classes/net/sourceforge/pain/logic/
classes/net/sourceforge/pain/logic/event/
classes/net/sourceforge/pain/logic/fn/util/
classes/net/sourceforge/pain/network/console/
classes/net/sourceforge/pain/plugin/
classes/net/sourceforge/pain/plugin/reset/
classes/net/sourceforge/pain/plugin/shutdown/
classes/net/sourceforge/pain/plugin/social/
classest/net/sourceforge/pain/db/data/
doc/
doc/paindb/resources/
src/net/sourceforge/pain/logic/
src/net/sourceforge/pain/logic/event/
src/net/sourceforge/pain/logic/fn/util/
src/net/sourceforge/pain/network/console/
src/net/sourceforge/pain/network/console/telnet/
src/net/sourceforge/pain/plugin/
src/net/sourceforge/pain/plugin/command/
src/net/sourceforge/pain/plugin/reset/
src/net/sourceforge/pain/plugin/shutdown/
src/net/sourceforge/pain/plugin/social/
src/net/sourceforge/pain/util/
tests/
tests/net/sourceforge/pain/db/data/
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;
	}

}