StateManagerImpl.java :  » Database-ORM » TJDO » com » triactive » jdo » state » Java Open Source

Java Open Source » Database ORM » TJDO 
TJDO » com » triactive » jdo » state » StateManagerImpl.java
/*
 * Copyright 2004 (C) TJDO.
 * All rights reserved.
 *
 * This software is distributed under the terms of the TJDO License version 1.0.
 * See the terms of the TJDO License in the documentation provided with this software.
 *
 * $Id: StateManagerImpl.java,v 1.19 2004/01/25 22:31:16 jackknifebarber Exp $
 */

package com.triactive.jdo.state;

import com.triactive.jdo.AbstractFieldManager;
import com.triactive.jdo.FieldManager;
import com.triactive.jdo.GenericFieldManager;
import com.triactive.jdo.PersistenceManager;
import com.triactive.jdo.SCO;
import com.triactive.jdo.model.ClassMetaData;
import com.triactive.jdo.model.FieldMetaData;
import com.triactive.jdo.sco.SCOProcessor;
import com.triactive.jdo.store.StoreManager;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import javax.jdo.InstanceCallbacks;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.JDOFatalUserException;
import javax.jdo.JDOUnsupportedOptionException;
import javax.jdo.JDOUserException;
import javax.jdo.Transaction;
import javax.jdo.spi.JDOImplHelper;
import javax.jdo.spi.PersistenceCapable;
import javax.jdo.spi.StateManager;
import org.apache.log4j.Category;


/**
 * This class implements the StateManager.
 *
 * @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
 * @version $Revision: 1.19 $
 */

public class StateManagerImpl implements com.triactive.jdo.StateManager
{
    private static final Category LOG = Category.getInstance(StateManagerImpl.class);
    private static final JDOImplHelper helper;
    private static final StateFieldManager hollowFieldManager = new StateFieldManager();

    private PersistenceManager myPM;
    private PersistenceCapable myPC;
    private Transaction myTX;
    private Object myID;
    private LifeCycleState myLC;
    private byte jdoFlags;
    private PersistenceCapable savedImage = null;
    private byte savedFlags;
    private boolean[] savedLoadedFields = null;

    private ClassMetaData cmd;
    private StoreManager srm;
    private int fieldCount;

    private boolean inserting = false;
    private boolean dirty = false;
    private boolean deleting = false;
    private boolean flushing = false;
    private boolean changingState = false;
    private boolean postLoadPending = false;
    private boolean disconnecting = false;
    private boolean[] dirtyFields;
    private boolean[] loadedFields;
    private boolean[] defaultFetchGroupFields;
    private boolean[] secondClassMutableFields;
    private int[] allFieldNumbers;
    private int[] persistentFieldNumbers;
    private int[] defaultFetchGroupFieldNumbers;
    private int[] secondClassMutableFieldNumbers;

    private FieldManager currFM = null;

    static
    {
        try
        {
            helper = JDOImplHelper.getInstance();
        }
        catch (SecurityException e)
        {
            throw new JDOFatalUserException("Insufficient access granted to com.triactive.jdo.*", e);
        }
    }


    /**
     * Constructs a state manager to manage an existing persistence-capable
     * instance that is not persistent.
     * All mutable SCO fields are wrapped in suitable wrapper objects and the
     * PC instance transitions to a TransientClean state.
     *
     * @param pm
     *      The persistence manager controlling this state manager.
     * @param pc
     *      The instance to be managed.
     */

    public StateManagerImpl(PersistenceManager pm, PersistenceCapable pc)
    {
        myPM = pm;
        myTX = pm.currentTransaction();
        srm = myPM.getStoreManager();
        myPC = pc;
        myID = null;
        myLC = LifeCycleState.getLifeCycleState(LifeCycleState.T_CLEAN);
        jdoFlags = PersistenceCapable.READ_OK;
        cmd = ClassMetaData.forClass(pc.getClass());

        initialize();

        for (int i = 0; i < fieldCount; ++i)
            loadedFields[i] = true;

        myPC.jdoReplaceStateManager(this);
        myPC.jdoReplaceFlags();

        replaceSCOFields();
    }


    /**
     * Constructs a state manager to manage a new, hollow instance having the
     * given object ID.
     * This constructor is used for creating new instances of existing
     * persistent objects (i.e. via pm.getObjectById()).
     *
     * @param pm
     *      The persistence manager controlling this state manager.
     * @param pcClass
     *      The class of the new instance to be created.
     * @param id
     *      The JDO identity of the object.
     */

    public StateManagerImpl(PersistenceManager pm, Class pcClass, Object id)
    {
        myPM = pm;
        myTX = pm.currentTransaction();
        srm = myPM.getStoreManager();
        myID = id;
        myLC = LifeCycleState.getLifeCycleState(LifeCycleState.HOLLOW);
        jdoFlags = PersistenceCapable.LOAD_REQUIRED;
        cmd = ClassMetaData.forClass(pcClass);

        initialize();

        myPC = helper.newInstance(pcClass, this);
    }


    private void initialize()
    {
        fieldCount = cmd.getInheritedFieldCount() + cmd.getFieldCount();

        dirtyFields = new boolean[fieldCount];
        loadedFields = (boolean[])cmd.getTransactionalFieldFlags().clone();
        defaultFetchGroupFields = cmd.getDefaultFetchGroupFieldFlags();
        secondClassMutableFields = cmd.getSecondClassMutableFieldFlags();

        allFieldNumbers = cmd.getAllFieldNumbers();
        persistentFieldNumbers = cmd.getPersistentFieldNumbers();
        defaultFetchGroupFieldNumbers = cmd.getDefaultFetchGroupFieldNumbers();
        secondClassMutableFieldNumbers = cmd.getSecondClassMutableFieldNumbers();
    }


    /**
     * Passes all SCO field values through replaceFields(), which will wrap any
     * unwrapped values with an appropriate SCO wrapper.
     */
    void replaceSCOFields()
    {
        StateFieldManager scoFieldValues = new StateFieldManager();

        provideFields(secondClassMutableFieldNumbers, scoFieldValues);
        replaceFields(secondClassMutableFieldNumbers, scoFieldValues);
    }


    Object wrapSCOInstance(int field, Object value)
    {
        if (value == null || (value instanceof SCO && ((SCO)value).getOwner() == myPC))
            return value;
        else
        {
            FieldMetaData fmd = cmd.getFieldAbsolute(field);
            Class fieldType = fmd.getType();

            SCOProcessor scoProc = SCOProcessor.forFieldType(fieldType);

            if (scoProc == null)
                throw new JDOUserException("Class not supported as a second-class object: " + fieldType.getName());

            return scoProc.newSCOInstance(myPC, fmd.getName(), value);
        }
    }


    /**
     * Calls unsetOwner() on all SCO fields.
     */
    private void disownSCOFields()
    {
        provideFields(secondClassMutableFieldNumbers, new AbstractFieldManager()
        {
            public void storeObjectField(int fieldNumber, Object value)
            {
                if (value instanceof SCO)
                    ((SCO)value).unsetOwner();
            }
        });
    }


    private boolean isDFGLoaded()
    {
        for (int i = 0; i < defaultFetchGroupFieldNumbers.length; ++i)
        {
            if (!loadedFields[defaultFetchGroupFieldNumbers[i]])
                return false;
        }

        return true;
    }


    void enlistInTransaction()
    {
        myPM.enlistInTransaction(this);

        if (jdoFlags == PersistenceCapable.LOAD_REQUIRED && isDFGLoaded())
        {
            /*
             * A transactional object whose DFG fields are loaded does not need
             * to contact us in order to read those fields.
             */
            jdoFlags = PersistenceCapable.READ_OK;
            myPC.jdoReplaceFlags();
        }
    }


    void evictFromTransaction()
    {
        myPM.evictFromTransaction(this);

        if (jdoFlags == PersistenceCapable.READ_OK && myLC.isPersistent())
        {
            /*
             * A non-transactional object needs to contact us on any field read no
             * matter what fields are loaded.
             */
            jdoFlags = PersistenceCapable.LOAD_REQUIRED;
            myPC.jdoReplaceFlags();
        }
    }


    void saveFields()
    {
        savedFlags = jdoFlags;
        savedLoadedFields = (boolean[])loadedFields.clone();
        savedImage = myPC.jdoNewInstance(this);
        savedImage.jdoCopyFields(myPC, allFieldNumbers);

        for (int i = 0; i < secondClassMutableFieldNumbers.length; ++i)
        {
            int field = secondClassMutableFieldNumbers[i];

            /*
             * Note: SCO fields in the saved image are clones and therefore
             * unowned.  If/when they are later restored the individual life-
             * cycle states decide whether or not they must be rewrapped to get
             * reowned (if going Transient they don't, otherwise they do).
             */
            Object value = provideField(savedImage, field);

            if (value != null)
            {
                if (value instanceof SCO)
                    value = ((SCO)value).clone();
                else
                {
                    Class c = value.getClass();

                    try
                    {
                        Method m = c.getMethod("clone", null);

                        if (m != null)
                            value = m.invoke(value, null);
                    }
                    catch (Exception e)
                    {
                        throw new JDOFatalInternalException("SCO class not cloneable: " + c.getName());
                    }
                }

                replaceField(savedImage, field, value);
            }
        }
    }


    void restoreFields()
    {
        if (savedImage != null)
        {
            loadedFields = savedLoadedFields;
            jdoFlags = savedFlags;
            myPC.jdoReplaceFlags();
            myPC.jdoCopyFields(savedImage, allFieldNumbers);

            clearDirtyFlags();
            discardSavedFields();
        }
    }


    void discardSavedFields()
    {
        savedFlags = 0;
        savedLoadedFields = null;
        savedImage = null;
    }


    void clearPersistentFields()
    {
        try
        {
            if (myPC instanceof InstanceCallbacks)
                ((InstanceCallbacks)myPC).jdoPreClear();
        }
        finally
        {
            replaceFieldsInternal(persistentFieldNumbers, hollowFieldManager);
            clearLoadedFlags();
            clearDirtyFlags();
        }
    }


    private void clearLoadedFlags()
    {
        jdoFlags = PersistenceCapable.LOAD_REQUIRED;
        myPC.jdoReplaceFlags();
        System.arraycopy(cmd.getTransactionalFieldFlags(), 0, loadedFields, 0, loadedFields.length);
    }


    private void clearDirtyFlags()
    {
        dirty = false;
        clearFlags(dirtyFields);
    }


    /**
     * Returns an array of integers containing the indices of all elements in
     * <tt>flags</tt> that are set to <tt>state</tt>.
     */

    private static int[] getFlagsSetTo(boolean[] flags, boolean state)
    {
        int[] temp = new int[flags.length];
        int j = 0;

        for (int i = 0; i < flags.length; i++)
        {
            if (flags[i] == state)
                temp[j++] = i;
        }

        if (j != 0)
        {
            int[] fieldNumbers = new int[j];
            System.arraycopy(temp, 0, fieldNumbers, 0, j);

            return fieldNumbers;
        }
        else
            return null;
    }


    /**
     * Returns an array of integers containing the indices of all elements in
     * <tt>flags</tt> whose index occurs in <tt>indices</tt> and whose value is
     * <tt>state</tt>.
     */

    private static int[] getFlagsSetTo(boolean[] flags, int[] indices, boolean state)
    {
        int[] temp = new int[indices.length];
        int j = 0;

        for (int i = 0; i < indices.length; i++)
        {
            if (flags[indices[i]] == state)
                temp[j++] = indices[i];
        }

        if (j != 0)
        {
            int[] fieldNumbers = new int[j];
            System.arraycopy(temp, 0, fieldNumbers, 0, j);

            return fieldNumbers;
        }
        else
            return null;
    }


    private static void clearFlags(boolean[] flags)
    {
        for (int i = 0; i < flags.length; i++)
            flags[i] = false;
    }


    private boolean disconnectClone(PersistenceCapable pc)
    {
        if (pc != myPC)
        {
            if (LOG.isDebugEnabled())
                LOG.debug("Disconnecting clone " + toJVMIDString(pc) + " from " + this);

            /*
             * Reset jdoFlags in the clone to PersistenceCapable.READ_WRITE_OK 
             * and clear its state manager.
             */
            byte myJdoFlags = jdoFlags;
            jdoFlags = PersistenceCapable.READ_WRITE_OK;

            try
            {
                pc.jdoReplaceFlags();
            }
            finally
            {
                jdoFlags = myJdoFlags;
            }

            pc.jdoReplaceStateManager(null);
            return true;
        }
        else
            return false;
    }


    /**
     * The StateManager uses this method to supply the value of jdoFlags to the
     * associated PersistenceCapable instance.
     *
     * @param pc
     *   the calling PersistenceCapable instance
     * @return
     *   the value of jdoFlags to be stored in the PersistenceCapable instance
     */

    public byte replacingFlags(PersistenceCapable pc)
    {
        // If this is a clone, return READ_WRITE_OK.
        if (pc != myPC)
            return PersistenceCapable.READ_WRITE_OK;
        else
            return jdoFlags;
    }


    /**
     * Return the PersistenceManager that owns this instance.
     *
     * @param pc
     *   the calling PersistenceCapable instance
     * @return
     *   the PersistenceManager that owns this instance
     */

    public javax.jdo.PersistenceManager getPersistenceManager(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return null;
        else
        {
            this.myPM.hereIsStateManager(this, myPC);
            return myPM;
        }
    }


    private void postWriteField(int field)
    {
        /*
         * If we've written a field in the middle of inserting or flushing it
         * must be due to jdoPreStore().  If inserting, we haven't actually done
         * the INSERT yet so we don't want to mark anything as dirty, which
         * would make us want to do an UPDATE later.
         */
        if (myLC.isPersistent() && !inserting)
        {
            dirty = true;
            dirtyFields[field] = true;
            loadedFields[field] = true;

            /*
             * If flushing, to avoid an infinite recursion we don't notify the
             * PM or call flush().
             */
            if (!flushing)
            {
                if (myTX.isActive())
                    myPM.markDirty(this);
                else
                    flush();
            }
        }
    }


    /**
     * Marks the given field dirty.
     */

    public void makeDirty(int field)
    {
        transitionWriteField();
        postWriteField(field);
    }


    /**
     * Mark the associated PersistenceCapable field dirty.
     *
     * @param pc the calling PersistenceCapable instance
     * @param fieldName the name of the field
     */

    public void makeDirty(PersistenceCapable pc, String fieldName)
    {
        if (!disconnectClone(pc))
        {
            int fieldNumber = cmd.getAbsoluteFieldNumber(fieldName);

            if (fieldNumber == -1)
                throw new JDOUserException("No such field '" + fieldName + "' in class " + cmd.getPCClass().getName());

            makeDirty(fieldNumber);
        }
    }


    public Object getObjectId()
    {
        return myID;
    }


    public StoreManager getStoreManager()
    {
        return srm;
    }


    public PersistenceManager getPersistenceManager()
    {
        return myPM;
    }


    /**
     * Return the object representing the JDO identity of the calling instance.
     *
     * According to the JDO specification, if the JDO identity is being changed
     * in the current transaction, this method returns the JDO identify as of the
     * beginning of the transaction.
     *
     * @param pc the calling PersistenceCapable instance
     * @return the object representing the JDO identity of the calling instance
     */

    public Object getObjectId(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return null;
        else
            return myID;
    }


    /**
     * Replace the current value of jdoStateManager.
     *
     * <P>This method is called by the PersistenceCapable whenever
     * jdoReplaceStateManager is called and there is already
     * an owning StateManager.  This is a security precaution
     * to ensure that the owning StateManager is the only
     * source of any change to its reference in the PersistenceCapable.</p>
     *
     * @return the new value for the jdoStateManager
     * @param pc the calling PersistenceCapable instance
     * @param sm the proposed new value for the jdoStateManager
     */

    public StateManager replacingStateManager(PersistenceCapable pc, StateManager sm)
    {
        if (myLC == null)
            throw new JDOFatalInternalException("Null LifeCycleState");

        if (pc == myPC)
        {
            if (sm != null)
                throw new JDOFatalInternalException("Attempted to replace with a different state manager");
            if (!disconnecting)
                throw new JDOFatalInternalException("Attempted to clear state manager from other than disconnect()");

            if (LOG.isDebugEnabled())
                LOG.debug("Clearing state manager for " + toJVMIDString(pc));

            return null;
        }
        else if (pc == savedImage)
            return null;
        else
            return sm;
    }


    /**
     * Return the object representing the JDO identity
     * of the calling instance.  If the JDO identity is being changed in
     * the current transaction, this method returns the current identity as
     * changed in the transaction.
     *
     * @param pc the calling PersistenceCapable instance
     * @return the object representing the JDO identity of the calling instance
     */

    public Object getTransactionalObjectId(PersistenceCapable pc)
    {
        return getObjectId(pc);
    }


    /**
     * Tests whether this object is dirty.
     *
     * Instances that have been modified, deleted, or newly
     * made persistent in the current transaction return true.
     *
     * <P>Transient nontransactional instances return false (JDO spec), but the
     * TriActive implementation does not currently support the transient
     * transactional state.
     *
     * @see PersistenceCapable#jdoMakeDirty(String fieldName)
     * @param pc the calling PersistenceCapable instance
     * @return true if this instance has been modified in the current transaction.
     */

    public boolean isDirty(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return false;
        else
            return myLC.isDirty();
    }


    /**
     * Tests whether this object is transactional.
     *
     * Instances that respect transaction boundaries return true.  These instances
     * include transient instances made transactional as a result of being the
     * target of a makeTransactional method call; newly made persistent or deleted
     * persistent instances; persistent instances read in data store
     * transactions; and persistent instances modified in optimistic transactions.
     *
     * <P>Transient nontransactional instances return false.
     *
     * @param pc the calling PersistenceCapable instance
     * @return true if this instance is transactional.
     */

    public boolean isTransactional(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return false;
        else
            return myLC.isTransactional();
    }


    /**
     * Tests whether this object is persistent.
     *
     * Instances whose state is stored in the data store return true.
     *
     * <P>Transient instances return false.
     *
     * @see PersistenceManager#makePersistent(Object pc)
     * @param pc the calling PersistenceCapable instance
     * @return true if this instance is persistent.
     */

    public boolean isPersistent(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return false;
        else
            return myLC.isPersistent();
    }


    /**
     * Tests whether this object has been newly made persistent.
     *
     * Instances that have been made persistent in the current transaction
     * return true.
     *
     * <P>Transient instances return false.
     *
     * @see PersistenceManager#makePersistent(Object pc)
     * @param pc the calling PersistenceCapable instance
     * @return true if this instance was made persistent
     * in the current transaction.
     */

    public boolean isNew(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return false;
        else
            return myLC.isNew();
    }


    /**
     * Tests whether this object has been deleted.
     *
     * Instances that have been deleted in the current transaction return true.
     *
     * <P>Transient instances return false.
     *
     * @see PersistenceManager#deletePersistent(Object pc)
     * @param pc the calling PersistenceCapable instance
     * @return true if this instance was deleted
     * in the current transaction.
     */

    public boolean isDeleted(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return false;
        else
            return myLC.isDeleted();
    }


    /**
     * Called whenever the default fetch group fields have all been loaded.
     * Updates jdoFlags and calls jdoPostLoad() as appropriate.
     * <p>
     * If it's called in the midst of a life-cycle transition both actions will
     * be deferred until the transition is complete.
     * <em>This deferral is important</em>.
     * Without it, we could enter user code (jdoPostLoad()) while still making
     * a state transition, and that way lies madness.
     * <p>
     * As an example, consider a jdoPostLoad() that calls other enhanced methods
     * that read fields (jdoPostLoad() itself is not enhanced).  A P_NONTRANS
     * object accessed within a transaction would produce the following infinite
     * loop:
     * <p>
     * <blockquote><pre>
     * isLoaded()
     * transitionReadField()
     * refreshLoadedFields()
     * jdoPostLoad()
     * isLoaded()
     * ...
     * </pre></blockquote>
     * <p>
     * because the transition from P_NONTRANS to P_CLEAN can never be completed.
     */

    private void postLoad()
    {
        if (changingState)
            postLoadPending = true;
        else
        {
            /*
             * A transactional object whose DFG fields are loaded does not need
             * to contact us in order to read those fields, so we can safely set
             * READ_OK.
             *
             * A non-transactional object needs to notify us on all field reads
             * so that we can decide whether or not any transition should occur,
             * so we leave the flags at LOAD_REQUIRED.
             */
            if (jdoFlags == PersistenceCapable.LOAD_REQUIRED && myLC.isTransactional())
            {
                jdoFlags = PersistenceCapable.READ_OK;
                myPC.jdoReplaceFlags();
            }

            if (myPC instanceof InstanceCallbacks)
                ((InstanceCallbacks)myPC).jdoPostLoad();
        }
    }


    /**
     * Validates that the instance exists in the data store.
     * Called by pm.getObjectById() when validate == true.
     */

    public void validate()
    {
        if (!myLC.isTransactional())
        {
            /*
             * We're either Hollow or PNonTrans.  If a TX is active and there
             * are any DFG fields then a retrieve() does what we want, i.e. it
             * validates existence and makes us PClean.  Otherwise we do the
             * minimum lookup operation and leave our state as it is.
             */
            if (myTX.isActive() && defaultFetchGroupFieldNumbers.length != 0)
                retrieve(true);
            else
                srm.lookup(this);
        }
    }


    /**
     * Offers the specified pre-fetched fields to the state manager.
     * If the instance is in a state that can benefit from newly available field
     * values, the fields are replaced in the instance and a state change occurs
     * as though the instance itself had read a field.
     * Called by pm.getObjectById() when given prefetched fields.
     */

    public void offerPrefetchedFields(int[] fieldNumbers, FieldManager fieldManager)
    {
        if (!myLC.isTransactional())
        {
            /* We're either Hollow or PNonTrans.  Accept the fields. */
            transitionReadField();

            boolean dfgWasAlreadyLoaded = isDFGLoaded();

            replaceFields(fieldNumbers, fieldManager);

            if (!dfgWasAlreadyLoaded && isDFGLoaded())
                postLoad();
        }
    }


    /**
     * Fetchs from the database all fields in the default fetch group not
     * already loaded.
     * Called by, or immediately after, life-cycle transitions.
     */

    void loadDFGFields()
    {
        int[] fieldNumbers = getFlagsSetTo(loadedFields, defaultFetchGroupFieldNumbers, false);

        if (fieldNumbers != null)
        {
            srm.fetch(this, fieldNumbers);
            postLoad();
        }
    }


    /**
     * Fetch a given field from the database.  Do NOT call with a default fetch
     * group field.
     *
     * @param fieldNumber   the field number of the field to fetch.
     */

    private void loadNonDFGField(int fieldNumber)
    {
        srm.fetch(this, new int[] { fieldNumber });
    }


    /**
     * Fetchs from the database all fields not currently loaded.
     * Called by life-cycle transitions.
     */

    void loadUnloadedFields()
    {
        int[] fieldNumbers = getFlagsSetTo(loadedFields, false);

        if (fieldNumbers != null)
        {
            boolean dfgWasAlreadyLoaded = isDFGLoaded();

            srm.fetch(this, fieldNumbers);

            /* If the DFG was not already loaded, it is now. */
            if (!dfgWasAlreadyLoaded)
                postLoad();
        }
    }


    /**
     * Refreshes from the database all fields currently loaded.
     * Called by life-cycle transitions.
     */

    void refreshLoadedFields()
    {
        int[] fieldNumbers = getFlagsSetTo(loadedFields, true);

        if (fieldNumbers != null)
        {
            clearDirtyFlags();
            clearLoadedFlags();

            srm.fetch(this, fieldNumbers);

            if (isDFGLoaded())
                postLoad();
        }
    }


    /**
     * Guarantee that the serializable transactional and persistent fields
     * are loaded into the instance.  This method is called by the generated
     * jdoPreSerialize method prior to serialization of the instance.
     *
     * @param pc the calling PersistenceCapable instance
     */

    public void preSerialize(PersistenceCapable pc)
    {
        if (disconnectClone(pc))
            return;

        retrieve(false);
    }


    /**
     * Return true if the field is cached in the calling instance.
     * <P>In the TriActive implementation of this method, isLoaded() will always
     * return true. If the field is not loaded, it will be loaded as a side effect
     * of the call to this method. If it is in the default fetch group, the default
     * fetch group, including this field, will be loaded.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the absolute field number
     * @return always returns true (this implementation)
     */

    public boolean isLoaded(PersistenceCapable pc, int field)
    {
        if (disconnectClone(pc))
            return true;
        else
        {
            transitionReadField();

            if (!loadedFields[field])
            {
                if (defaultFetchGroupFields[field])
                    loadDFGFields();
                else
                    loadNonDFGField(field);
            }

            return true;
        }
    }


    /**
     * Reads the current value of the specified field.
     * This has the same effect on the state of the instance as if user code
     * attempted to read the field.
     *
     * @param field
     *      The field number to read.
     * @return
     *      The value of the field.  Primitives are boxed in appropriate
     *      java.lang wrapper classes.
     */

    public Object getField(int field)
    {
        isLoaded(myPC, field);
        return provideField(myPC, field);
    }


    /**
     * Updates the current value of the specified field.
     * This has the same effect on the state of the instance as if user code
     * attempted to write the field.
     *
     * @param field
     *      The field number to write.
     * @param currentValue
     *      The current value of the field.  Used to determine whether or not
     *      the write is redundant.  If necessary, the value can be obtained
     *      using {@link #getField}.
     * @param newValue
     *      The new value of the field.  Primitives must be boxed in appropriate
     *      java.lang wrapper classes.
     */

    public void setField(int field, Object currentValue, Object newValue)
    {
        if (!loadedFields[field] || currentValue != newValue)
            writeObjectField(field, currentValue, newValue);
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public boolean getBooleanField(PersistenceCapable pc, int field, boolean currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public byte getByteField(PersistenceCapable pc, int field, byte currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public char getCharField(PersistenceCapable pc, int field, char currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public double getDoubleField(PersistenceCapable pc, int field, double currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public float getFloatField(PersistenceCapable pc, int field, float currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public int getIntField(PersistenceCapable pc, int field, int currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public long getLongField(PersistenceCapable pc, int field, long currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public short getShortField(PersistenceCapable pc, int field, short currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public String getStringField(PersistenceCapable pc, int field, String currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * This method is called by the associated PersistenceCapable if the
     * value for the specified field is not cached (i.e., StateManager.isLoaded()
     * fails). In this implementation of the StateManager, isLoaded() has a
     * side effect of loading unloaded information and will always return true.
     * As such, this method should never be called.
     */

    public Object getObjectField(PersistenceCapable pc, int field, Object currentValue)
    {
        throw new JDOUnsupportedOptionException("Method not supported");
    }


    /**
     * Called by the various setXXXField() methods once it's established that
     * the field really deserves to be written.
     * Makes the state transition and replaces the field value in the PC
     * instance.
     */

    private void writeField(int field, Object newValue)
    {
        transitionWriteField();
        replaceField(myPC, field, newValue);
        postWriteField(field);
    }


    /**
     * Called by setObjectField() once it's established that the field really
     * deserves to be written.
     * Makes the state transition and replaces the field value in the PC
     * instance.
     * Wraps SCO fields if needed.
     */

    private void writeObjectField(int field, Object currentValue, Object newValue)
    {
        transitionWriteField();

        if (secondClassMutableFields[field])
        {
            if (currentValue instanceof SCO)
                ((SCO)currentValue).unsetOwner();

            newValue = wrapSCOInstance(field, newValue);
        }

        replaceField(myPC, field, newValue);
        postWriteField(field);
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setBooleanField(PersistenceCapable pc, int field, boolean currentValue, boolean newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, newValue ? Boolean.TRUE : Boolean.FALSE);
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, newValue ? Boolean.TRUE : Boolean.FALSE);
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setByteField(PersistenceCapable pc, int field, byte currentValue, byte newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Byte(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Byte(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setCharField(PersistenceCapable pc, int field, char currentValue, char newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Character(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Character(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setDoubleField(PersistenceCapable pc, int field, double currentValue, double newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Double(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Double(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setFloatField(PersistenceCapable pc, int field, float currentValue, float newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Float(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Float(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setIntField(PersistenceCapable pc, int field, int currentValue, int newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Integer(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Integer(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setLongField(PersistenceCapable pc, int field, long currentValue, long newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Long(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Long(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setShortField(PersistenceCapable pc, int field, short currentValue, short newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, new Short(newValue));
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeField(field, new Short(newValue));
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setStringField(PersistenceCapable pc, int field, String currentValue, String newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, newValue);
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || !equals(currentValue, newValue))
                writeField(field, newValue);
        }
    }


    /**
     * This method is called by the associated PersistenceCapable when the
     * corresponding mutator method (setXXX()) is called on the PersistenceCapable.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @param currentValue the current value of the field
     * @param newValue the new value for the field
     */

    public void setObjectField(PersistenceCapable pc, int field, Object currentValue, Object newValue)
    {
        if (pc != myPC)
        {
            replaceField(pc, field, newValue);
            disconnectClone(pc);
        }
        else
        {
            if (!loadedFields[field] || currentValue != newValue)
                writeObjectField(field, currentValue, newValue);
        }
    }


    /**
     * Compares two objects for equality, where one or both of the object
     * references may be null.
     *
     * @return  <code>true</code> if the objects are both <code>null</code> or
     *          compare equal according to their equals() method,
     *          <code>false</code> otherwise.
     */

    private static boolean equals(Object o1, Object o2)
    {
        return o1 == null ? (o2 == null) : o1.equals(o2);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedBooleanField(PersistenceCapable pc, int field, boolean currentValue)
    {
        currFM.storeBooleanField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedByteField(PersistenceCapable pc, int field, byte currentValue)
    {
        currFM.storeByteField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedCharField(PersistenceCapable pc, int field, char currentValue)
    {
        currFM.storeCharField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedDoubleField(PersistenceCapable pc, int field, double currentValue)
    {
        currFM.storeDoubleField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedFloatField(PersistenceCapable pc, int field, float currentValue)
    {
        currFM.storeFloatField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedIntField(PersistenceCapable pc, int field, int currentValue)
    {
        currFM.storeIntField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedLongField(PersistenceCapable pc, int field, long currentValue)
    {
        currFM.storeLongField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedShortField(PersistenceCapable pc, int field, short currentValue)
    {
        currFM.storeShortField(field, currentValue);
    }



    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedStringField(PersistenceCapable pc, int field, String currentValue)
    {
        currFM.storeStringField(field, currentValue);
    }


    /**
     * This method is called from the associated PersistenceCapable when its
     * PersistenceCapable.jdoProvideFields() method is invoked. Its purpose is
     * to provide the value of the specified field to the StateManager.
     *
     * @param pc   the calling PersistenceCapable instance
     * @param field   the field number
     * @param currentValue   the current value of the field
     */

    public void providedObjectField(PersistenceCapable pc, int field, Object currentValue)
    {
        currFM.storeObjectField(field, currentValue);
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a boolean field.
     *
     * @param pc the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public boolean replacingBooleanField(PersistenceCapable pc, int field)
    {
        boolean value = currFM.fetchBooleanField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a byte field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public byte replacingByteField(PersistenceCapable obj, int field)
    {
        byte value = currFM.fetchByteField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a char field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public char replacingCharField(PersistenceCapable obj, int field)
    {
        char value = currFM.fetchCharField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a double field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public double replacingDoubleField(PersistenceCapable obj, int field)
    {
        double value = currFM.fetchDoubleField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a float field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public float replacingFloatField(PersistenceCapable obj, int field)
    {
        float value = currFM.fetchFloatField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a int field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public int replacingIntField(PersistenceCapable obj, int field)
    {
        int value = currFM.fetchIntField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a long field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public long replacingLongField(PersistenceCapable obj, int field)
    {
        long value = currFM.fetchLongField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a short field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public short replacingShortField(PersistenceCapable obj, int field)
    {
        short value = currFM.fetchShortField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of a String field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public String replacingStringField(PersistenceCapable obj, int field)
    {
        String value = currFM.fetchStringField(field);
        loadedFields[field] = true;

        return value;
    }


    /**
     * This method is invoked by the PersistenceCapable object's
     * jdoReplaceField() method to refresh the value of an Object field.
     *
     * @param obj the calling PersistenceCapable instance
     * @param field the field number
     * @return the new value for the field
     */

    public Object replacingObjectField(PersistenceCapable obj, int field)
    {
        Object value = currFM.fetchObjectField(field);
        loadedFields[field] = true;

        return value;
    }


    public PersistenceCapable getObject()
    {
        return myPC;
    }


    private synchronized Object provideField(PersistenceCapable pc, int fieldNumber)
    {
        Object obj;

        FieldManager prevFM = currFM;
        currFM = new StateFieldManager();

        try
        {
            pc.jdoProvideField(fieldNumber);
            return currFM.fetchObjectField(fieldNumber);
        }
        finally
        {
            currFM = prevFM;
        }
    }


    private synchronized void replaceField(PersistenceCapable pc, int fieldNumber, final Object value)
    {
        FieldManager prevFM = currFM;
        currFM = new GenericFieldManager()
        {
            public Object fetchObjectField(int field)
            {
                return value;
            }

            public void storeObjectField(int field, Object value) {}    // not possible
        };

        try
        {
            pc.jdoReplaceField(fieldNumber);
        }
        finally
        {
            currFM = prevFM;
        }
    }


    /**
     * Called from the StoreManager after StoreManager.update() is called to obtain
     * updated values from the PersistenceCapable associated with this StateManager.
     *
     * @param fieldNumbers
     *   An array of field numbers to be updated by the Store
     * @param fm
     *   The updated values are stored in this object. This object is only valid
     *   for the duration of this call.
     */

    public synchronized void provideFields(int fieldNumbers[], FieldManager fm)
    {
        FieldManager prevFM = currFM;
        currFM = fm;

        try
        {
            myPC.jdoProvideFields(fieldNumbers);
        }
        finally
        {
            currFM = prevFM;
        }
    }


    /**
     * Called to refresh data in the PersistenceCapable object associated with
     * this StateManager.
     * <p>
     * Unwrapped values for SCO fields are wrapped with an appropriate SCO
     * wrapper object.
     *
     * @param fieldNumbers
     *      An array of field numbers to be refreshed.
     * @param fm
     *      Provides the updated values. This object is only used for the
     *      duration of the call.
     */

    public synchronized void replaceFields(int fieldNumbers[], FieldManager fm)
    {
        replaceFieldsInternal(fieldNumbers, new SCOWrapper(fm, this, secondClassMutableFields));
    }


    private synchronized void replaceFieldsInternal(int fieldNumbers[], FieldManager fm)
    {
        FieldManager prevFM = currFM;
        currFM = fm;

        try
        {
            myPC.jdoReplaceFields(fieldNumbers);
        }
        finally
        {
            currFM = prevFM;
        }
    }


    private void preStateChange()
    {
        changingState = true;
    }


    private void postStateChange()
    {
        changingState = false;

        if (postLoadPending)
        {
            postLoadPending = false;
            postLoad();
        }
    }


    /**
     * Makes the instance persistent.
     * If the object is already persistent, this method does nothing.
     * Otherwise, a new object ID for the instance is obtained from the store
     * manager and the object is inserted in the data store.
     * <p>
     * Any failure will leave the instance in its previous life-cycle state.
     *
     * @return
     *      <code>true</code> if the object was successfully made persistent,
     *      <code>false</code> if the object was already persistent.
     */

    public boolean makePersistent()
    {
        if (myLC.isPersistent())
            return false;

        LifeCycleState oldLC = myLC;
        preStateChange();
        try { myLC = myLC.transitionMakePersistent(this, myTX); }
        finally { postStateChange(); }

        boolean succeeded = false;

        try
        {
            myID = srm.newObjectID(myPC.getClass());

            if (inserting)
                throw new JDOFatalInternalException("makePersistent() called recursively");

            inserting = true;

            try
            {
                if (myPC instanceof InstanceCallbacks)
                    ((InstanceCallbacks)myPC).jdoPreStore();

                srm.insert(this);
            }
            finally
            {
                inserting = false;
            }

            succeeded = true;
        }
        finally
        {
            if (!succeeded)
            {
                preStateChange();
                try { myLC = myLC.transitionRollbackState(this, oldLC); }
                finally { postStateChange(); }

                myID = null;
            }
        }

        myPM.dataStoreModified();

        return true;
    }


    private void transitionReadField()
    {
        preStateChange();
        try { myLC = myLC.transitionReadField(this, myTX); }
        finally { postStateChange(); }
    }


    private void transitionWriteField()
    {
        preStateChange();
        try { myLC = myLC.transitionWriteField(this, myTX); }
        finally { postStateChange(); }
    }


    public void makeTransactional()
    {
        preStateChange();
        try { myLC = myLC.transitionMakeTransactional(this, myTX); }
        finally { postStateChange(); }
    }


    public void makeNontransactional()
    {
        preStateChange();
        try { myLC = myLC.transitionMakeNontransactional(this); }
        finally { postStateChange(); }
    }


    public void makeTransient()
    {
        preStateChange();
        try { myLC = myLC.transitionMakeTransient(this); }
        finally { postStateChange(); }
    }


    public void evict()
    {
        preStateChange();
        try { myLC = myLC.transitionEvict(this); }
        finally { postStateChange(); }
    }


    public void refresh()
    {
        preStateChange();
        try { myLC = myLC.transitionRefresh(this, myTX); }
        finally { postStateChange(); }
    }


    public void retrieve(boolean DFGOnly)
    {
        preStateChange();
        try { myLC = myLC.transitionRetrieve(this, myTX, DFGOnly); }
        finally { postStateChange(); }
    }


    /**
     * This method is invoked when a commit is performed in a Transaction
     * involving the PersistenceCapable managed by this StateManager
     */

    public void postCommit()
    {
        preStateChange();
        try { myLC = myLC.transitionCommit(this, myTX); }
        finally { postStateChange(); }
    }


    /**
     * This method is invoked when a rollback is performed in a Transaction
     * involving the PersistenceCapable managed by this StateManager.
     */

    public void preRollback()
    {
        preStateChange();
        try { myLC = myLC.transitionRollback(this, myTX); }
        finally { postStateChange(); }
    }


    void preDelete()
    {
        if (myPC instanceof InstanceCallbacks)
            ((InstanceCallbacks)myPC).jdoPreDelete();
    }


    /**
     * Deletes the instance.
     * If the object is already deleted, this method does nothing.
     * Otherwise, the object is removed from the data store.
     * <p>
     * Any failure will leave the instance in its previous life-cycle state.
     */

    public void deletePersistent()
    {
        if (myLC.isDeleted())
            return;

        if (deleting)
            throw new JDOFatalInternalException("deletePersistent() called recursively");

        deleting = true;

        try
        {
            LifeCycleState oldLC = myLC;
            preStateChange();
            try { myLC = myLC.transitionDeletePersistent(this, myTX); }
            finally { postStateChange(); }

            boolean succeeded = false;

            try
            {
                srm.delete(this);
                clearLoadedFlags();

                succeeded = true;
            }
            finally
            {
                if (!succeeded)
                {
                    preStateChange();
                    try { myLC = myLC.transitionRollbackState(this, oldLC); }
                    finally { postStateChange(); }
                }
            }
        }
        finally
        {
            deleting = false;
        }

        myPM.dataStoreModified();
    }


    /**
     * Flushes any dirty fields to the data store.
     *
     * <p>Note that "dirty" in this case is not equated to being in the P_DIRTY
     * state.  The P_DIRTY state means that at least one field in the object has
     * been written by the user during the current transaction, whereas for the
     * purposes of this method, a field is "dirty" if it's been written by the
     * user but not yet updated in the data store.  The difference is, it's
     * possible for an object's state to be P_DIRTY, yet have no "dirty" fields
     * because flush() has been called at least once during the transaction.
     */

    public void flush()
    {
        if (dirty)
        {
            if (flushing)
                throw new JDOFatalInternalException("flush() re-entered");

            flushing = true;

            try
            {
                if (myPC instanceof InstanceCallbacks && !deleting)
                    ((InstanceCallbacks)myPC).jdoPreStore();

                int[] dirtyFieldNumbers = getFlagsSetTo(dirtyFields, true);

                if (dirtyFieldNumbers == null)
                    throw new JDOFatalInternalException("dirty == true but no fields are marked dirty");

                srm.update(this, dirtyFieldNumbers);

                clearDirtyFlags();
            }
            finally
            {
                flushing = false;
            }

            myPM.dataStoreModified();
        }
    }


    void disconnect()
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Disconnecting " + toJVMIDString(myPC) + " from " + this);

        disownSCOFields();
        myPM.removeStateManager(this);
        jdoFlags = PersistenceCapable.READ_WRITE_OK;
        myPC.jdoReplaceFlags();

        disconnecting = true;

        try
        {
            myPC.jdoReplaceStateManager(null);
        }
        finally
        {
            disconnecting = false;
        }

        discardSavedFields();
        myPM = null;
        myPC = null;
        myID = null;
        myLC = null;
        cmd = null;
        srm = null;
    }


    public String toString()
    {
        if (myPC == null)
            return "StateManager@" + System.identityHashCode(this) + "(disconnected)";
        else
        {
            String pcClassName = myPC.getClass().getName();

            return "StateManager:" + pcClassName.substring(pcClassName.lastIndexOf('.') + 1)
                 + '@' + System.identityHashCode(myPC) + '(' + myID + ')';
        }
    }


    /* ------------------------ debugging helper methods -------------------- */


    private static String toJVMIDString(Object obj)
    {
        if (obj == null)
            return "null";
        else
            return obj.getClass().getName() + '@' + System.identityHashCode(obj);
    }


    private static String booleanArrayToString(boolean[] ba)
    {
        if (ba == null)
            return "null";

        StringBuffer sb = new StringBuffer("[");

        for (int i = 0; i < ba.length; ++i)
            sb.append(ba[i] ? 'Y' : 'N');

        sb.append(']');

        return sb.toString();
    }


    private static String intArrayToString(int[] ia)
    {
        if (ia == null)
            return "null";

        StringBuffer sb = new StringBuffer("[");

        for (int i = 0; i < ia.length; ++i)
        {
            if (i > 0)
                sb.append(',');

            sb.append(ia[i]);
        }

        sb.append(']');

        return sb.toString();
    }


    private static String jdoFlagsToString(byte flags)
    {
        switch (flags)
        {
            case PersistenceCapable.LOAD_REQUIRED:
                return "LOAD_REQUIRED";
            case PersistenceCapable.READ_OK:
                return "READ_OK";
            case PersistenceCapable.READ_WRITE_OK:
                return "READ_WRITE_OK";
            default:
                return "???";
        }
    }


    private static void dumpPC(PersistenceCapable pc, PrintWriter out)
    {
        out.println(toJVMIDString(pc));

        if (pc == null)
            return;

        out.print("jdoStateManager = "); out.println(peekField(pc, "jdoStateManager"));
        out.print("jdoFlags = ");
        Object flagsObj = peekField(pc, "jdoFlags");

        if (flagsObj instanceof Byte)
            out.println(jdoFlagsToString(((Byte)flagsObj).byteValue()));
        else
            out.println(flagsObj);

        Class c = pc.getClass();

        do
        {
            String[] fieldNames = helper.getFieldNames(c);

            for (int i = 0; i < fieldNames.length; ++i)
            {
                out.print(fieldNames[i]); out.print(" = "); out.println(peekField(pc, fieldNames[i]));
            }

            c = c.getSuperclass();
        } while (c != null && PersistenceCapable.class.isAssignableFrom(c));
    }


    private static Object peekField(Object obj, String fieldName)
    {
        try
        {
            /*
             * This doesn't work due to security problems but you get the idea.
             * I'm trying to get field values directly without going through
             * the provideField machinery.
             */
            Object value = obj.getClass().getDeclaredField(fieldName).get(obj);

            if (value instanceof PersistenceCapable)
                return toJVMIDString(value);
            else
                return value;
        }
        catch (Exception e)
        {
            return e.toString();
        }
    }


    public void dump(PrintWriter out)
    {
        out.print("myPM = "); out.println(myPM);
        out.print("myTX = "); out.println(myTX);
        out.print("myID = "); out.println(myID);
        out.print("myLC = "); out.println(myLC);
        out.print("cmd = "); out.println(cmd);
        out.print("srm = "); out.println(srm);
        out.print("fieldCount = "); out.println(fieldCount);
        out.print("dirty = "); out.println(dirty);
        out.print("flushing = "); out.println(flushing);
        out.print("changingState = "); out.println(changingState);
        out.print("postLoadPending = "); out.println(postLoadPending);
        out.print("disconnecting = "); out.println(disconnecting);
        out.print("dirtyFields = "); out.println(booleanArrayToString(dirtyFields));
        out.print("defaultFetchGroupFields = "); out.println(booleanArrayToString(defaultFetchGroupFields));
        out.print("secondClassMutableFields = "); out.println(booleanArrayToString(secondClassMutableFields));
        out.print("allFieldNumbers = "); out.println(intArrayToString(allFieldNumbers));
        out.print("persistentFieldNumbers = "); out.println(intArrayToString(persistentFieldNumbers));
        out.print("defaultFetchGroupFieldNumbers = "); out.println(intArrayToString(defaultFetchGroupFieldNumbers));
        out.print("secondClassMutableFieldNumbers = "); out.println(intArrayToString(secondClassMutableFieldNumbers));

        out.println();
        out.print("jdoFlags = "); out.println(jdoFlagsToString(jdoFlags));
        out.print("loadedFields = "); out.println(booleanArrayToString(loadedFields));
        out.print("myPC = "); dumpPC(myPC, out);

        out.println();
        out.print("savedFlags = "); out.println(jdoFlagsToString(savedFlags));
        out.print("savedLoadedFields = "); out.println(booleanArrayToString(savedLoadedFields));
        out.print("savedImage = "); dumpPC(savedImage, out);
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.