/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id: ClassMolder.java 8749 2010-09-16 08:50:29Z wguttmn $
 */
package org.exolab.castor.persist;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Messages;
import org.castor.cpa.util.JDOClassDescriptorResolver;
import org.castor.jdo.util.ClassLoadingUtils;
import org.castor.persist.ProposedEntity;
import org.castor.persist.TransactionContext;
import org.castor.persist.UpdateAndRemovedFlags;
import org.castor.persist.UpdateFlags;
import org.castor.persist.resolver.ResolverFactory;
import org.castor.persist.resolver.ResolverStrategy;
import org.exolab.castor.jdo.ObjectModifiedException;
import org.exolab.castor.jdo.ObjectNotFoundException;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.Persistent;
import org.exolab.castor.jdo.TimeStampable;
import org.exolab.castor.jdo.engine.JDOCallback;
import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature;
import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.TypeConvertor;
import org.exolab.castor.mapping.loader.ClassDescriptorHelper;
import org.exolab.castor.mapping.loader.ClassDescriptorImpl;
import org.exolab.castor.mapping.loader.FieldHandlerImpl;
import org.exolab.castor.mapping.xml.ClassMapping;
import org.exolab.castor.mapping.xml.NamedNativeQuery;
import org.exolab.castor.persist.spi.CallbackInterceptor;
import org.exolab.castor.persist.spi.Identity;
import org.exolab.castor.persist.spi.Persistence;
import org.exolab.castor.xml.ClassDescriptorResolver;
import org.exolab.castor.xml.ResolverException;

/**
 * ClassMolder is a 'binder' for one type of data object and its corresponding 
 * {@link Persistence}. For example, when ClassMolder is asked to load
 * an object, it acquires the field values from {@link Persistence} and binds 
 * them into the target object. 
 * <p>
 * It resolves relations via {@link TransactionContext} and subsequently binds 
 * these related objects into the target object, too.
 * <p>
 * Apart from loading, ClassMolder is also responsible for storing, removing,
 * creating an object to and from a persistence storage, as well as
 * reverting an object to its previous state.
 * <p>
 * Each instance of ClassMolder deals with exactly one persistable type,
 * interacts with one instance of Persistent and belongs to one
 * {@link LockEngine}.
 * <p>
 *
 * @author <a href="yip@intalio.com">Thomas Yip</a>
 * @author <a href="mailto:ferret AT frii dot com">Bruce Snyder</a>
 * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
 */

public class ClassMolder {
    /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
     *  Commons Logging</a> instance used for all logging. */
    private static Log _log = LogFactory.getFactory().getInstance(ClassMolder.class);
    
    /** The fully qualified name of the java data object class which this ClassMolder
     *  corresponds to. We call it base class. */
    private String _name;

    /** Associated identity <tt>FieldMolder</tt>s. */
    private FieldMolder[] _ids;

    /** Associated field <tt>FieldMolder</tt>s. */
    private FieldMolder[] _fhs;

    /** <tt>ClassMolder</tt> of the java data object class's ClassMolder which
     *  the base class extends; null if this class does not extend any other 
     *  classes. */
    private ClassMolder _extends;

    /** <tt>ClassMolder</tt> of the java data object class's ClassMolder which
     *  the base class depends on. <tt>null</tt> if it class is an indenpendent
     *  class. */
    private ClassMolder _depends;

    /** A Vector of <tt>ClassMolder</tt>s for all the direct dependent class of the
     *  base class. */
    private Vector<ClassMolder> _dependent;

    /** A Vector of <tt>ClassMolder</tt>s for all the direct extending class of the
     *  base class. */
    private Vector<ClassMolder> _extendent;

    /** Default accessMode of the base class. */
    private AccessMode _accessMode;

    /** Associated {@link Persistence} instance. */
    private Persistence _persistence;

    /** Associated {@link LockEngine} instance. */
    private LockEngine _engine;

    /** The CallbackInterceptor for the base class. */
    private CallbackInterceptor _callback;

    /** The parameters to be used for caching freed instance of the base class. */
    private Properties _cacheParams;

    /** Is a key kenerator used for the base class? */
    public boolean _isKeyGenUsed;

    /** Create priority. */
    private int _priority = -1;

    /** True if all {@link ResolverStrategy} have been reset. */
    private boolean _resolversHaveBeenReset = false;
        
    /** All field resolver instances. */
    private ResolverStrategy[] _resolvers;
    
    /** ClassDescriptor for the class this molder is responsible for. */
    private final ClassDescriptor _clsDesc;

    /**
     * Creates an instance of this class.
     * 
     * @param ds is the helper class for resolving depends and extends relationship
     *        among all the ClassMolder in the same LockEngine.
     * @param classDescriptorResolver {@link ClassDescriptorResolver} instance
     * @param lock the lock engine.
     * @param classDescriptor the classDescriptor for the base class.
     * @param persistenceEngine the Persistence for the base class.
     * @throws ClassNotFoundException If a class cannot be loaded.
     * @throws MappingException if an error occurred with analyzing the mapping information.
     */
    ClassMolder(final DatingService ds, final ClassDescriptorResolver classDescriptorResolver, 
            final LockEngine lock, final ClassDescriptor classDescriptor, 
            final Persistence persistenceEngine)
            throws ClassNotFoundException, MappingException {

        _engine = lock;
        _persistence = persistenceEngine;
        _clsDesc = classDescriptor;
        
        _name = classDescriptor.getJavaClass().getName();
        ds.register(_name, this);
        
        if (classDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature nature = new ClassDescriptorJDONature(classDescriptor);
            _accessMode = nature.getAccessMode();
        }
        
        dealWithExtendsAndDepends(ds, classDescriptor);

        if (classDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature nature = new ClassDescriptorJDONature(classDescriptor);
            _cacheParams = nature.getCacheParams();
            _isKeyGenUsed = nature.getKeyGeneratorDescriptor() != null;
        }

        // construct <tt>FieldMolder</tt>s for each of the identity fields of
        // the base class.
        
        FieldDescriptor[] identityDescriptors = ClassDescriptorHelper.getIdFields(classDescriptor);
        
        _ids = new FieldMolder[identityDescriptors.length];
        int m = 0;
        for (FieldDescriptor identityDescriptor : identityDescriptors) {
            _ids[m++] = new FieldMolder(ds, this, identityDescriptor);
        }

        // construct <tt>FieldModlers</tt>s for each of the non-transient fields 
        // of the base class 
        
        FieldDescriptor[] fieldDescriptors = ClassDescriptorHelper.getFullFields(classDescriptor);
        
        int numberOfNonTransientFieldMolders = 0;
        for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
            if (!isFieldTransient(fieldDescriptor)) {
                numberOfNonTransientFieldMolders += 1;
            }
        }

        _fhs = new FieldMolder[numberOfNonTransientFieldMolders];
        _resolvers = new ResolverStrategy[numberOfNonTransientFieldMolders];
        
        int fieldMolderCount = 0;
        for (FieldDescriptor fieldDescriptor : fieldDescriptors) {


            // don't create field molder for transient fields
            if (isFieldTransient(fieldDescriptor)) {
                continue;
            }

            if (fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName()) && 
                    new FieldDescriptorJDONature(fieldDescriptor).getManyTable() != null) {
                FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldDescriptor);
                
                // the fields is not primitive
                String[] relatedIdSQL = null;
                int[] relatedIdType = null;
                TypeConvertor[] relatedIdConvertTo = null;
                TypeConvertor[] relatedIdConvertFrom = null;

                String manyTable = nature.getManyTable();

                String[] idSQL = new String[identityDescriptors.length];
                int[] idType = new int[identityDescriptors.length];
                TypeConvertor[] idConvertFrom = new TypeConvertor[identityDescriptors.length];
                TypeConvertor[] idConvertTo = new TypeConvertor[identityDescriptors.length];
                FieldDescriptor[] identityFieldDescriptors = ((ClassDescriptorImpl) classDescriptor).getIdentities();
                int identityFieldCount = 0;
                for (FieldDescriptor identityFieldDescriptor : identityFieldDescriptors) {
                    if (identityFieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) {
                        idSQL[identityFieldCount] = new FieldDescriptorJDONature(identityFieldDescriptor).getSQLName()[0];
                        int[] type = new FieldDescriptorJDONature(identityFieldDescriptor).getSQLType();
                        idType[identityFieldCount] = (type == null) ? 0 : type[0];
                        FieldHandlerImpl fieldHandler = (FieldHandlerImpl) identityFieldDescriptor.getHandler();
                        idConvertTo[identityFieldCount] = fieldHandler.getConvertTo();
                        idConvertFrom[identityFieldCount] = fieldHandler.getConvertFrom();
                    } else {
                        throw new MappingException(
                                "Identity type must contains sql information: " + _name);
                    }
                    identityFieldCount++;
                }

                ClassDescriptor relatedClassDescriptor = null;
                try {
                    JDOClassDescriptorResolver jdoCDR = (JDOClassDescriptorResolver) classDescriptorResolver;
                    relatedClassDescriptor = jdoCDR.resolve(fieldDescriptor.getFieldType().getName());
                } catch (ResolverException e) {
                    throw new MappingException("Problem resolving class descriptor for class " 
                            + fieldDescriptor.getClass().getName(), e);
                }

                if (relatedClassDescriptor.hasNature(ClassDescriptorJDONature.class.getName())) {
                    FieldDescriptor[] relatedIdentityDescriptors = ((ClassDescriptorImpl) relatedClassDescriptor).getIdentities();
                    relatedIdSQL = new String[relatedIdentityDescriptors.length];
                    relatedIdType = new int[relatedIdentityDescriptors.length];
                    relatedIdConvertTo = new TypeConvertor[relatedIdentityDescriptors.length];
                    relatedIdConvertFrom = new TypeConvertor[relatedIdentityDescriptors.length];
                    int relatedIdentityCount = 0;
                    for (FieldDescriptor relatedIdentityDescriptor : relatedIdentityDescriptors) {
                        if (relatedIdentityDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) {
                            FieldDescriptorJDONature relatedNature = new FieldDescriptorJDONature(relatedIdentityDescriptor);
                            String[] tempId = relatedNature.getSQLName();
                            relatedIdSQL[relatedIdentityCount] = (tempId == null) ? null : tempId[0];
                            int[] tempType =  relatedNature.getSQLType();
                            relatedIdType[relatedIdentityCount] = (tempType == null) ? 0 : tempType[0];
                            FieldHandlerImpl fh = (FieldHandlerImpl) relatedIdentityDescriptors[relatedIdentityCount].getHandler();
                            relatedIdConvertTo[relatedIdentityCount] = fh.getConvertTo();
                            relatedIdConvertFrom[relatedIdentityCount] = fh.getConvertFrom();
                        } else {
                            throw new MappingException(
                                    "Field type is not persistence-capable: "
                                    + relatedIdentityDescriptors[relatedIdentityCount].getFieldName());
                        }
                        relatedIdentityCount++;
                    }
                }

                // if many-key exist, idSQL is overridden
                String[] manyKey = nature.getManyKey();
                if ((manyKey != null) && (manyKey.length != 0)) {
                    if (manyKey.length != idSQL.length) {
                        throw new MappingException(
                                "The number of many-keys doesn't match referred object: "
                                + classDescriptor.getJavaClass().getName());
                    }
                    idSQL = manyKey;
                }

                // if name="" exist, relatedIdSQL is overridden
                String[] manyName = nature.getSQLName();
                if ((manyName != null) && (manyName.length != 0)) {
                    if (manyName.length != relatedIdSQL.length) {
                        throw new MappingException(
                                "The number of many-keys doesn't match referred object: "
                                + relatedClassDescriptor.getJavaClass().getName());
                    }
                    relatedIdSQL = manyName;
                }

                SQLRelationLoader loader = _persistence.createSQLRelationLoader(
                        manyTable, idSQL, idType, idConvertTo, idConvertFrom,
                        relatedIdSQL, relatedIdType, relatedIdConvertTo, relatedIdConvertFrom);
                _fhs[fieldMolderCount] = new FieldMolder(ds, this, fieldDescriptor, loader);
            } else {
                _fhs[fieldMolderCount] = new FieldMolder(ds, this, fieldDescriptor);
            }

            // create RelationResolver instance
            _resolvers[fieldMolderCount] = ResolverFactory.createRelationResolver(
                    _fhs[fieldMolderCount], this, fieldMolderCount);

            fieldMolderCount += 1;
        }

        // ssa, FIXME : Are the two statements equivalents ?
        //        if ( Persistent.class.isAssignableFrom( _base ) )
        if (Persistent.class.isAssignableFrom(ds.resolve(_name))) {
            _callback = new JDOCallback();
        }
    }

    /**
     * Remaining method that relies on the usage of {@link ClassMapping} to extract
     * information from the JDO mapping file rather than relying on {@link ClassDescriptor}
     * and associated JDO-specific natures.
     * 
     * @param ds {@link DatingService} instance.
     * @param classDescriptor The {@link ClassDescriptor} instance used to describe the class at hand.
     * @throws MappingException If something unforeseen happens ....
     */
    private void dealWithExtendsAndDepends(final DatingService ds,
            final ClassDescriptor classDescriptor) throws MappingException {

        ClassDescriptor dependClassDescriptor = ((ClassDescriptorImpl) classDescriptor).getDepends();
        ClassDescriptor extendsClassDescriptor = ((ClassDescriptorImpl) classDescriptor).getExtends();

        //if ( dep != null && ext != null )
        //    throw new MappingException("A JDO cannot both extends and depends on other objects");

        if (dependClassDescriptor != null) {
            ds.pairDepends(this, dependClassDescriptor.getJavaClass().getName());
        }

        if (extendsClassDescriptor != null) {
            ds.pairExtends(this, extendsClassDescriptor.getJavaClass().getName());
        }
    }
    
    public ClassDescriptor getClassDescriptor() { 
        return _clsDesc; 
    }
    
    private boolean isFieldTransient(final FieldDescriptor fieldDescriptor) {
        boolean isFieldTransient = fieldDescriptor.isTransient();
        if (fieldDescriptor.hasNature(FieldDescriptorJDONature.class.getName())) {
            FieldDescriptorJDONature nature = new FieldDescriptorJDONature(fieldDescriptor);
            isFieldTransient |= nature.isTransient();
        }
        return isFieldTransient;
    }

    /**
     * Remove the reference of a related object from an object of
     * the base class. <p>
     *
     * If the related object is PersistanceCapable, the field will
     * be set null. If the related object is a Collection, then
     * the related object will be removed from the Collection. <p>
     *
     * If any changed occurred, transactionContext.markModified
     * will be called, to indicate the object is modified. <p>
     *
     * It method will iterate through all of the object's field and
     * try to remove all the occurrence.
     *
     * @param tx the TransactionContext of the transaction in action
     * @param object the target object of the base type of this ClassMolder
     * @param relatedMolder the ClassMolder of the related object to be
     *                       removed from the object
     * @param relatedObject the object to be removed
     */
    public boolean removeRelation(final TransactionContext tx, final Object object,
            final ClassMolder relatedMolder, final Object relatedObject) {

        boolean removed = false;
        boolean updateCache = false;
        boolean updatePersist = false;
        
        UpdateAndRemovedFlags flags = null;
        for (int i = 0; i < _fhs.length; i++) {
            flags = _resolvers[i].removeRelation(tx, object, relatedMolder,
                    relatedObject);
            updateCache |= flags.getUpdateCache();
            updatePersist |= flags.getUpdatePersist();
            removed |= flags.getRemoved();
        }
        tx.markModified(object, updatePersist, updateCache);
        return removed;
    }

    /**
     * Determines the create priority of the data object class represented by
     * this ClassMolder. Concpetually, this method determines the order of
     * which data object should be created.
     *  
     * A priority of 0 indicates that an object represented by this ClassMolder
     * can be created independently, without having to consider any other 
     * data object.
     *
     * This method should only be called after DatingService is closed.
     */
    public int getPriority() {
        if (_priority == -2) {
            // circular reference detected, do not loop
            return 0;
        } else if (_priority < 0) {
            // find root of extends hierarchy
            ClassMolder root = this;
            while (root._extends != null) { root = root._extends; }
            
            // find all class molders of extends hierarchy
            List<ClassMolder> molders = getAllExtendentMolders(root);
            
            // mark all class molders of extends hierarchy as work in progress
            for (ClassMolder molder : molders) { molder._priority = -2; }
            
            // preset maximum priority
            int maxPriority = 0;
            
            // calculate maximum priority from all classes referenced by the whole hierarchy
            for (ClassMolder molder : molders) {
                FieldMolder[] fhs = molder._fhs;
                for (int i = 0; i < fhs.length; i++) {
                    FieldMolder fh = fhs[i];
                    if (fh.isPersistanceCapable() && fh.isStored()
                            && (fh.getFieldClassMolder() != this)) {
                        int refPriority = fh.getFieldClassMolder().getPriority() + 1;
                        maxPriority = Math.max(maxPriority, refPriority);
                    }
                }
            }

            // set priority of the whole hierarchy to the calculated maximum
            for (ClassMolder molder : molders) { molder._priority = maxPriority; }
        }
        
        return _priority;
    }
    
    private List<ClassMolder> getAllExtendentMolders(final ClassMolder root) {
        List<ClassMolder> molders = new ArrayList<ClassMolder>();
        molders.add(root);
        
        if (root._extendent != null) {
            for (ClassMolder extendent : root._extendent) {
                molders.addAll(getAllExtendentMolders(extendent));
            }
        }
        return molders;
    }

    public void loadTimeStamp(final TransactionContext tx, final DepositBox locker,
            final AccessMode suggestedAccessMode)
    throws PersistenceException {
        Object loadObject = null;
        try {
            loadObject = newInstance(tx.getClassLoader());
        } catch (Exception ex) {
            throw new PersistenceException("failed to load object", ex);
        }

        ProposedEntity proposedObject = new ProposedEntity(this);
        proposedObject.setProposedEntityClass(loadObject.getClass());
        proposedObject.setEntity(loadObject);
        
        OID oid = locker.getOID();
        
        Object[] cachedFieldValues = locker.getObject(tx);
        proposedObject.setFields(cachedFieldValues);
        
        AccessMode accessMode = getAccessMode(suggestedAccessMode);

        // load the fields from the persistent storage if the cache is empty
        // or the access mode is DBLOCKED (thus guaranteeing that a lock at the
        // database level will be created)
        if (!proposedObject.isFieldsSet() || accessMode == AccessMode.DbLocked) {
            proposedObject.initializeFields(_fhs.length);

            Connection conn = tx.getConnection(oid.getMolder().getLockEngine());
            _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode);

            oid.setDbLock(accessMode == AccessMode.DbLocked);
            
            // store (new) field values to cache
            locker.setObject(tx, proposedObject.getFields(), System.currentTimeMillis());
        }

        mold(tx, locker, proposedObject, accessMode);
    }

    /**
     * Loads the field values.
     * 
     * @param tx Currently active transaction context
     * @param locker Current cache instance
     * @param proposedObject ProposedEntity instance
     * @param accessMode Suggested access mode
     * @param results OQL QueryResults instance
     * @throws ObjectNotFoundException If the object in question cannot be found.
     * @throws PersistenceException For any other persistence-related problem.
     */
    public void load(final TransactionContext tx, final DepositBox locker,
            final ProposedEntity proposedObject, final AccessMode accessMode,
            final QueryResults results)
    throws PersistenceException {
        OID oid = locker.getOID();
        if (oid.getIdentity() == null) {
            throw new PersistenceException(
                    "The identities of the object to be loaded is null");
        }
        
        proposedObject.initializeFields(_fhs.length);
        if (results != null) {
            results.getQuery().fetch(proposedObject);
        } else {
            Connection conn = tx.getConnection(oid.getMolder().getLockEngine());
            _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode);
        }

        oid.setDbLock(accessMode == AccessMode.DbLocked);
        
        // store (new) field values to cache
        locker.setObject(tx, proposedObject.getFields(), System.currentTimeMillis());
    }

    public void mold(final TransactionContext tx, final DepositBox locker,
            final ProposedEntity proposedObject, final AccessMode accessMode)
    throws PersistenceException {
        OID oid = locker.getOID();

        // Check for version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();

            // Check if version field was set and has content.
            // TODO: that check should be moved to e.g. ClassDescriptorJDONature
            if (versionField != null && versionField.length() > 0) {
                // Find field descriptor for version field.
                FieldDescriptor versionFieldDescriptor = jdoNature.getField(versionField);
                FieldHandler fieldHandler = versionFieldDescriptor.getHandler();
                // Set the entity's version to the locker's version.
                fieldHandler.setValue(proposedObject.getEntity(), locker.getVersion());
            }
        }

        // set the timeStamp of the data object to locker's timestamp
        if (proposedObject.getEntity() instanceof TimeStampable) {
            ((TimeStampable) proposedObject.getEntity()).jdoSetTimeStamp(locker
                    .getVersion());
        }
        
        // set the identities into the target object
        setIdentity(tx, proposedObject.getEntity(), oid.getIdentity());

        // iterates over all the field of the object and bind all field.
        for (int i = 0; i < _fhs.length; i++) {
            FieldPersistenceType fieldType = _fhs[i].getFieldPertsistenceType();
            
            switch (fieldType) {
            case PRIMITIVE:
            case SERIALIZABLE:
            case PERSISTANCECAPABLE:
            case ONE_TO_MANY:
            case MANY_TO_MANY:
                _resolvers[i].load(tx, oid, proposedObject, accessMode);
                break;
            default:
                throw new PersistenceException("Unexpected field type!");
            }
        }
        
        // Check for version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature jdoNature =
                    new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();

            // Check if version field was set and has content.
            if (versionField != null && versionField.length() > 0) {
                // Find field descriptor for version field.
                FieldDescriptor versionFieldDescriptor =
                        jdoNature.getField(versionField);
                FieldHandler fieldHandler = versionFieldDescriptor.getHandler();
                // Set the version of the locker to the data object's version.
                locker.setVersion((Long) fieldHandler.getValue(proposedObject
                        .getEntity()));
            }
        }

        // set the timeStamp of locker to the one of data object
        if (proposedObject.getEntity() instanceof TimeStampable) {
            locker.setVersion(((TimeStampable) proposedObject.getEntity())
                    .jdoGetTimeStamp());
        }
    }

    /**
     * Create an object of the base class with specified identity into the persistence storage.
     *
     * @param tx   transaction in action
     * @param oid  the object identity of the object to be created.
     * @param locker the dirty checking cache of the object
     * @param object  the object to be created
     * @return  the identity of the object
     */
    public Identity create(final TransactionContext tx, final OID oid, final DepositBox locker,
            final Object object) throws PersistenceException {

        if (_persistence == null) {
            throw new PersistenceException("non persistence capable: "
                    + oid.getName());
        }

        ProposedEntity entity = new ProposedEntity();
        entity.initializeFields(_fhs.length);
        Identity ids = oid.getIdentity();

        long timeStamp = System.currentTimeMillis();

        // Check for version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();

            // Check if version field was set and has content.
            // TODO: that check should be moved to e.g. ClassDescriptorJDONature
            if (versionField != null && versionField.length() > 0) {
                // Find field descriptor for version field.
                FieldDescriptor versionFieldDescriptor =
                        jdoNature.getField(versionField);
                FieldHandler fieldHandler = versionFieldDescriptor.getHandler();
                fieldHandler.setValue(object, timeStamp);
            }
        }
        
        // set the new timeStamp into the data object
        if (object instanceof TimeStampable) {
            ((TimeStampable) object).jdoSetTimeStamp(timeStamp);
        }

        // copy the object to cache should make a new field now,
        for (int i = 0; i < _fhs.length; i++) {
            FieldPersistenceType fieldPersistenceType = _fhs[i].getFieldPertsistenceType();

            switch (fieldPersistenceType) {
            case PRIMITIVE:
            case PERSISTANCECAPABLE:
            case SERIALIZABLE:
            case ONE_TO_MANY:
            case MANY_TO_MANY:
                entity.setField(_resolvers[i].create(tx, object), i);
                break;

            default:
                throw new IllegalArgumentException("Field type invalid!");
            }
        }
        
        // ask Persistent to create the object into the persistence storage
        Identity createdId = _persistence.create(tx.getDatabase(),
                tx.getConnection(oid.getMolder().getLockEngine()), entity, ids);

        if (createdId == null) {
            throw new PersistenceException("Identity can't be created!");
        }

        // set the field values into the cache
        locker.setObject(tx, entity.getFields(), timeStamp);
        oid.setDbLock(true);

        // set the identity into the object
        setIdentity(tx, object, createdId);

        // after successful creation, add the entry in the relation table for
        // all many-to-many relationship
        //ASE: This is the source of problem with M:N relations. As we see at
        //     this point, for every persisted object in any M:N relation, a
        //	   new entry to the relation table is created. But this happens not
        //	   only when both related objects are persisted but also after
        //	   persistence of the first. But that cannot work!
        // 	   A solution would be to store a collection of relations which have
        //	   to persisted in the relation table after all objects are persisted 
        //	   independently!
        
        for (int i = 0; i < _fhs.length; i++) {
            entity.setField(_resolvers[i].postCreate(
                    tx, oid, object, entity.getField(i), createdId), i);
        }

        return createdId;
    }


    /**
     * Walk the object model and mark object that should be created.
     *
     * @param tx   transaction in action
     * @param oid  the object identity of the object to be created.
     * @param locker the dirty checking cache of the object
     * @param object  the object to be created
     */
    public void markCreate(final TransactionContext tx, final OID oid, final DepositBox locker,
            final Object object) throws PersistenceException {

        boolean updateCache = false;

        // iterate all the fields and mark all the dependent object.
        for (int i = 0; i < _fhs.length; i++) {
            updateCache |= _resolvers[i].markCreate(tx, oid, object);
        }

        tx.markModified(object, false, updateCache);

    }

    /**
     * Check the object for modification. If dpendent object is dereferenced, it
     * method will remove the object thru the transaction. If an related object
     * is dereferenced, it method will make sure the formally object will be
     * dereferenced from the other side as well.
     *
     * This method is called in prepare (for commit) state of the transaction.
     * This method indicates if the object needed to be persist or cache should
     * be update using TransactionContext.markDelete.
     *
     * @param tx transaction in action
     * @param oid the object identity of the object
     * @param locker the dirty check cache for the object
     * @param object the data object to be checked
     * @param timeout  timeout of updating the lock if needed
     *
     * @return true if the object is modified
     */
    public boolean preStore(final TransactionContext tx, final OID oid, final DepositBox locker,
            final Object object, final int timeout) throws PersistenceException {

        if (oid.getIdentity() == null) {
            throw new PersistenceException(Messages.format(
                    "persist.missingIdentityForStore", _name));
        }

        if (!oid.getIdentity().equals(getIdentity(tx, object))) {
            throw new PersistenceException(Messages.format(
                    "jdo.identityChanged", _name, oid.getIdentity(),
                    getIdentity(tx, object)));
        }

        Object[] fields = locker.getObject(tx);

        if (fields == null) {
            throw new PersistenceException(
                    Messages.format("persist.objectNotFound", _name, oid));
        }

        Object[] newfields = new Object[_fhs.length];
        boolean updateCache = false;
        boolean updatePersist = false;

        // iterate thru all the data object fields for modification
        UpdateFlags flags;
        for (int i = 0; i < newfields.length; i++) {
            flags = _resolvers[i].preStore(tx, oid, object, timeout, fields[i]);
            updateCache |= flags.getUpdateCache();
            updatePersist |= flags.getUpdatePersist();
            newfields[i] = flags.getNewField();
        }

        tx.markModified(object, updatePersist, updateCache);

        if (updateCache || updatePersist) {
            tx.writeLock(object, timeout);
        }

        return updateCache;
    }

    /**
     * Store a data object into the persistent storage of the base class of this
     * ClassMolder.
     *
     * @param tx Transaction in action
     * @param oid the object identity of the stored object
     * @param locker the dirty check cache of the object
     * @param object the object to be stored
     * @throws PersistenceException If identity is missing  for storage
     * or the identity is modified
     */
    public void store(final TransactionContext tx, final OID oid, final DepositBox locker,
            final Object object) throws PersistenceException {

        if (oid.getIdentity() == null) {
            throw new PersistenceException(Messages.format(
                    "persist.missingIdentityForStore", _name));
        }

        if (!oid.getIdentity().equals(getIdentity(tx, object))) {
            throw new PersistenceException(Messages.format(
                    "jdo.identityChanged", _name, oid.getIdentity(),
                    getIdentity(tx, object)));
        }

        // load field values from cache (if availabe)
        ProposedEntity oldentity = new ProposedEntity();
        oldentity.setFields(locker.getObject(tx));

        if (oldentity.getFields() == null) {
            throw new PersistenceException(
                    Messages.format("persist.objectNotFound", _name, oid));
        }

        long timeStamp = System.currentTimeMillis();

        // Set the new timestamp into version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();

            // Check if version field was set and has content.
            // TODO: that check should be moved to e.g. ClassDescriptorJDONature
            if (versionField != null && versionField.length() > 0) {
                // Find field descriptor for version field.
                FieldDescriptor versionFieldDescriptor =
                        jdoNature.getField(versionField);
                FieldHandler fieldHandler = versionFieldDescriptor.getHandler();
                fieldHandler.setValue(object, timeStamp);
            }
        }

        // set the new timeStamp into the data object
        if (object instanceof TimeStampable) {
            ((TimeStampable) object).jdoSetTimeStamp(timeStamp);
        }

        ProposedEntity newentity = new ProposedEntity();
        newentity.initializeFields(_fhs.length);
        for (int i = 0; i < _fhs.length; i++) {
            newentity.setField(_resolvers[i].store(tx, object, oldentity.getField(i)), i);
        }
        
        // Gets connection reference
        Connection conn = tx.getConnection(oid.getMolder().getLockEngine());

        // Current molder is leaf of extends hierarchy and therefore extending table is null
        String extendingTableName = null;

        // Start with current molder
        ClassMolder molder = this;
        
        // Loop over all extended molders and store all values of extends hierarchy
        while (molder != null) {
            // Gets name of current table
            String tableName = new ClassDescriptorJDONature(
                    molder.getClassDescriptor()).getTableName();
            
            // Only need to persist values if current table name is different than extending one
            if (!tableName.equals(extendingTableName)) {
                molder._persistence.store(conn, oid.getIdentity(), newentity, oldentity);
            }
         
            extendingTableName = tableName;
            molder = molder._extends;
        }        
    }

    /**
     * Update the object which loaded or created in the other transaction to the
     * persistent storage.
     * 
     * @param tx
     *            Transaction in action
     * @param oid
     *            the object identity of the stored object
     * @param locker
     *            the dirty check cache of the object
     * @param object
     *            the object to be stored
     * @return boolean true if the updating object should be created
     */
    public boolean update(final TransactionContext tx, final OID oid,
            final DepositBox locker, final Object object,
            final AccessMode suggestedAccessMode) throws PersistenceException {

        AccessMode accessMode = getAccessMode(suggestedAccessMode);

        Object[] fields = locker.getObject(tx);

        boolean timeStampable = false;
        long objectTimestamp = 1;
        
        if (object instanceof TimeStampable) {
            timeStampable = true;
            objectTimestamp = ((TimeStampable) object).jdoGetTimeStamp();
        }
        
        ClassDescriptorJDONature jdoNature = null;

        // Check for version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            jdoNature = new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();
            // TODO: that check should be moved to e.g. ClassDescriptorJDONature
            if (versionField != null && versionField.length() > 0) {
                objectTimestamp =  getObjectVersion(versionField, jdoNature, object);
                timeStampable = true;
            }
        }
        
        if ((!isDependent()) && (!timeStampable)) {
            throw new IllegalArgumentException(
                    "A master object that involves in a long transaction must be a TimeStampable!");
        }

        long lockTimestamp = locker.getVersion();

        if ((objectTimestamp > 0) && (oid.getIdentity() != null)) {
            // valid range of timestamp

            if ((timeStampable)
                    && (lockTimestamp == TimeStampable.NO_TIMESTAMP)) {
                throw new PersistenceException(Messages.format(
                        "persist.objectNotInCache", _name, oid.getIdentity()));
            }

            if (timeStampable && objectTimestamp != lockTimestamp) {
                throw new ObjectModifiedException("Timestamp mismatched!");
            }

            if (!timeStampable && isDependent() && (fields == null)) {
                // allow a dependent object not implements timeStampable
                fields = new Object[_fhs.length];
                Connection conn =
                        tx.getConnection(oid.getMolder().getLockEngine());

                ProposedEntity proposedObject = new ProposedEntity(this);
                proposedObject.setProposedEntityClass(object.getClass());
                proposedObject.setEntity(object);
                proposedObject.setFields(fields);
                _persistence.load(conn, proposedObject, oid.getIdentity(),
                        accessMode);
                fields = proposedObject.getFields();

                oid.setDbLock(accessMode == AccessMode.DbLocked);
                locker.setObject(tx, proposedObject.getFields(), System
                        .currentTimeMillis());
            }

            // load the original field into the transaction. so, store will
            // have something to compare later.
            try {
                for (int i = 0; i < _fhs.length; i++) {
                    _resolvers[i]
                            .update(tx, oid, object, accessMode, fields[i]);
                }
            } catch (ObjectNotFoundException e) {
                _log.warn(e.getMessage(), e);
                throw new ObjectModifiedException(
                        "dependent object deleted concurrently");
            }
            return false;
        } else if ((objectTimestamp == TimeStampable.NO_TIMESTAMP)
                || (objectTimestamp == 1)) {
            // work almost like create, except update the sub field instead of
            // create
            // iterate all the fields and mark all the dependent object.
            boolean updateCache = false;

            for (int i = 0; i < _fhs.length; i++) {
                updateCache |=
                        _resolvers[i].updateWhenNoTimestampSet(tx, oid, object,
                                suggestedAccessMode);
            }

            tx.markModified(object, false, updateCache);
            return true;
        } else {
            if (_log.isWarnEnabled()) {
                _log.warn("object: " + object + " timestamp: "
                        + objectTimestamp + " lockertimestamp: "
                        + lockTimestamp);
            }
            throw new ObjectModifiedException(
                    "Invalid object timestamp detected.");
        }
    }

    /**
     * Acquires the version of the specified object by accessing the given
     * field.
     * 
     * @param versionField
     *            the version field.
     * @param jdoNature
     *            the {@link ClassDescriptorJDONature}.
     * @param object
     *            the object.
     * @return the object's version.
     */
    private Long getObjectVersion(final String versionField,
            final ClassDescriptorJDONature jdoNature, final Object object) {
        FieldDescriptor versionFieldDescriptor =
                jdoNature.getField(versionField);
        return (Long) versionFieldDescriptor.getHandler().getValue(object);
    }

    /**
     * Update the dirty checking cache. This method is called after a 
     * transaction completed successfully.
     *
     * @param tx - transaction in action
     * @param oid - object's identity of the target object
     * @param locker - the dirty checking cache of the target object
     * @param object - the target object
     */
    public void updateCache(final TransactionContext tx, 
            final OID oid, 
            final DepositBox locker, 
            final Object object) {

        Object[] fields;

        if (oid.getIdentity() == null) {
            throw new IllegalStateException(
                    Messages.format("persist.missingIdentityForCacheUpdate", _name));
        }

        fields = new Object[_fhs.length];

        for (int i = 0; i < _fhs.length; i++) {
            FieldPersistenceType fieldPersistenceType = _fhs[i].getFieldPertsistenceType();
            switch (fieldPersistenceType) {
            case PRIMITIVE:
            case SERIALIZABLE:
            case PERSISTANCECAPABLE:
            case ONE_TO_MANY:
            case MANY_TO_MANY:
                fields[i] = _resolvers[i].updateCache(tx, oid, object);
                break;
            default:
                throw new IllegalArgumentException("Field type invalid!");
            }
        }
        
        Long objectVersion = null;
        
        // Check for version field.
        if (_clsDesc.hasNature(ClassDescriptorJDONature.class.getName())) {
            ClassDescriptorJDONature jdoNature = new ClassDescriptorJDONature(_clsDesc);
            String versionField = jdoNature.getVersionField();
            // TODO: that check should be moved to e.g. ClassDescriptorJDONature
            if (versionField != null && versionField.length() > 0) {
                objectVersion = getObjectVersion(versionField, jdoNature, object);
            }
        }
        
        // store new field values in cache
        if (object instanceof TimeStampable) {
            locker.setObject(tx, fields, ((TimeStampable) object).jdoGetTimeStamp());
        } else if (objectVersion != null) {
            locker.setObject(tx, fields, objectVersion);
        } else {
            locker.setObject(tx, fields, System.currentTimeMillis());
        }
    }

    /**
     * Delete an object of the base type from the persistence storage.
     * All object to be deleted by this method will be <tt>markDelete</tt>
     * before it method is called.
     *
     * @param tx - transaction in action
     * @param oid - the object identity of the target object
     */
    public void delete(final TransactionContext tx, final OID oid) throws PersistenceException {
        Connection conn = tx.getConnection(oid.getMolder().getLockEngine());
        Identity ids = oid.getIdentity();

        for (int i = 0; i < _fhs.length; i++) {
            if (_fhs[i].isManyToMany()) {
                _fhs[i].getRelationLoader().deleteRelation(conn, ids);
            }
        }

        // Must delete record of extend path from extending to root class
        // In addition we remember the extend path to delete everything off the path ourself
        Vector<ClassMolder> extendPath = new Vector<ClassMolder>();
        ClassMolder molder = this;
        while (molder != null) {
            molder._persistence.delete(conn, ids);
            extendPath.add(molder);
            molder = molder._extends;
        }
        
        ClassMolder base = _depends;
        while (base != null) {
            if (base._extendent != null) {
                for (int i = 0; i < base._extendent.size(); i++) {
                    if (extendPath.contains(base._extendent.get(i))) {
                        // NB: further INVESTIGATION
                    }
                }
            }

            base = base._extends;
        }
    }

    /**
     * Prepare to delete an object with the specified identity. If any sub-object
     * should be deleted along with the target object, it should be deleted
     * by this method.
     *
     * @param tx - transaction in action
     * @param oid - object's identity of the target object
     * @param locker - the dirty checking cache of the target object
     * @param object - the target object
     */
    public void markDelete(final TransactionContext tx, final OID oid, final DepositBox locker,
            final Object object) throws PersistenceException {

        Object[] fields = locker.getObject(tx);

        for (int i = 0; i < _fhs.length; i++) {
            FieldPersistenceType fieldType = _fhs[i].getFieldPertsistenceType();
            switch (fieldType) {
            case PRIMITIVE:
            case SERIALIZABLE:
            case PERSISTANCECAPABLE:
            case ONE_TO_MANY:
            case MANY_TO_MANY:
                _resolvers[i].markDelete(tx, object, fields[i]);
                break;
            default:
                throw new PersistenceException("Invalid field type!");
            }
        }
    }

    /**
     * Revert the object back to the state of begining of the transaction
     * If the object is loaded, it will be revert as it was loaded. If the
     * object is created, it will be revert as it was just created.
     *
     * @param tx - transaction in action
     * @param oid - the object identity of the target object
     * @param locker - the dirty checking cache of the target object
     * @param object - the target object
     */
    public void revertObject(final TransactionContext tx, 
            final OID oid, 
            final DepositBox locker, 
            final Object object)
            throws PersistenceException {

        if (oid.getIdentity() == null) {
            throw new PersistenceException(
                    Messages.format("persist.missingIdentityForReverting", _name));
        }

        Object[] fields = locker.getObject(tx);

        setIdentity(tx, object, oid.getIdentity());

        for (int i = 0; i < _fhs.length; i++) {
            if (fields != null) {
                _resolvers[i].revertObject(tx, oid, object, fields[i]);
            } else {
                _resolvers[i].revertObject(tx, oid, object, null);
            }
        }
    }

    /**
     * Return a new instance of the base class with the provided ClassLoader object.
     *
     * @param loader the ClassLoader object to use to create a new object.
     * @return Object the object reprenseted by this ClassMolder, and instanciated with the
     *         provided ClassLoader instance.
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Object newInstance(final ClassLoader loader)
    throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> aClass = null;
        aClass = ClassLoadingUtils.loadClass(loader, _name);
        return aClass.newInstance();
    }

    /**
     * Get the effective accessMode of the the base type.
     * 
     * @param txMode - the default transaction accessMode.
     * @return the effective acessMode of the base type.
     */
    public AccessMode getAccessMode(final AccessMode txMode) {

        if (txMode == null) {
            return _accessMode;
        }

        if (_accessMode == AccessMode.ReadOnly || txMode == AccessMode.ReadOnly) {
            return AccessMode.ReadOnly;
        }
        if (_accessMode == AccessMode.DbLocked || txMode == AccessMode.DbLocked) {
            return AccessMode.DbLocked;
        }
        if (_accessMode == AccessMode.Exclusive
                || txMode == AccessMode.Exclusive) {
            return AccessMode.Exclusive;
        }
        return txMode;

    }

    /**
     * Get the callback interceptor of the base type.
     */
    public CallbackInterceptor getCallback() {
        return _callback;
    }

    /**
     * Test if the specified identity is the default value of the type.
     */
    public boolean isDefaultIdentity(final Identity identity) {
        if (identity == null) { return true; }
        for (int i = 0; i < identity.size(); i++) {
            if (!_ids[i].isDefault(identity.get(i))) { return false; }
        }
        return true;
    }

    /**
     * Get the identity from a object of the base type.
     * If object isn't persistent and key generator is used, returns null.
     *
     * @param tx the transaction context.
     * @param o - object of the base type.
     * @return return an Object[] which contains the identity of the object.
     */
    public Identity getIdentity(final TransactionContext tx, final Object o) {
        // [oleg] In the case where key generator is used,
        // the value of identity is dummy, set it to null
        if (isKeyGeneratorUsed() && !(tx.isPersistent(o) || tx.isReadOnly(o))) {
            return null;
        }
        return getActualIdentity(tx, o);
    }

    /**
     * Get the identity from a object of the base type.
     *
     * @param tx the transaction context.
     * @param o - object of the base type.
     * @return return an Object[] which contains the identity of the object.
     */
    public Identity getActualIdentity(final TransactionContext tx, final Object o) {
        return getActualIdentity(tx.getClassLoader(), o);
    }

    /**
     * Get the identity from a object of the base type.
     *
     * @param loader the current class loader.
     * @param o - object of the base type.
     * @return return an Object[] which contains the identity of the object.
     */
    public Identity getActualIdentity(final ClassLoader loader, final Object o) {
        Object[] ids = new Object[_ids.length];
        for (int i = 0; i < ids.length; i++) {
            ids[i] = _ids[i].getValue(o, loader);
        }
        if (ids[0] == null) { return null; }
        return new Identity(ids);
    }

    /**
     * Set the identity into an object.
     *
     * @param tx the transaction context.
     * @param object the object to set the identity.
     * @param identity the new identity for the object.
     */
    public void setIdentity(final TransactionContext tx, 
            final Object object, 
            final Identity identity)
            throws PersistenceException {

        if (identity.size() != _ids.length) {
            throw new PersistenceException("Identity size mismatched!");
        }

        for (int i = 0; i < _ids.length; i++) {
            _ids[i].setValue(object, identity.get(i), tx.getClassLoader());
        }
    }
    
    /**
     * Get the Persisetence of the base type.
     */
    public Persistence getPersistence() {
        return _persistence;
    }

    /**
     * Get the base class of this ClassMolder given a ClassLoader.
     * 
     * @param loader the classloader.
     * @return the <code>Class</code> instance.
     */
    public Class<?> getJavaClass(final ClassLoader loader) {
        Class<?> result = null;
        try {
            result = ClassLoadingUtils.loadClass(loader, _name);
        } catch (ClassNotFoundException e) {
            _log.error("Unable to load base class of " + getName(), e);
        }
        return result;
    }

    // ssa, FIXME : is that method necessary ?
    /**
     * check if the current ClassModlder is assignable from the <code>class</code>
     * instance.
     *
     * @param cls the Class to check the assignation
     * @return true if assignable
     */
    public boolean isAssignableFrom (final Class<?> cls) {
        ClassLoader loader = cls.getClassLoader();
        Class<?> molderClass = null;
        try {
            molderClass = ClassLoadingUtils.loadClass(loader, _name);
        } catch (ClassNotFoundException e) {
            return false;
        }
        return molderClass.isAssignableFrom(cls);
    }

    /**
     * Get the fully qualified name of the base type of this ClassMolder.
     */
    public String getName() {
        return _name;
    }

    /**
     * Get the extends class' ClassMolder.
     */
    public ClassMolder getExtends() {
        return _extends;
    }

    /**
     * Get the depends class' ClassMolder.
     */
    public ClassMolder getDepends() {
        return _depends;
    }

    /**
     * Get the LockEngine which this ClassMolder belongs to.
     */
    public LockEngine getLockEngine() {
        return _engine;
    }

    /**
     * Returns the active cache parameters.
     * 
     * @return Active cache parameters.
     */
    public Properties getCacheParams() {
        return _cacheParams;
    }

    /**
     * Return true if the base type of this ClassMolder is an dependent class.
     */
    public boolean isDependent() {
        return _depends != null;
    }

    /**
     * Mutator method to add a extent ClassMolder.
     */
    void addExtendent(final ClassMolder ext) {
        if (_extendent == null) {
            _extendent = new Vector<ClassMolder>();
        }
        _extendent.add(ext);
    }

    /**
     * Mutator method to add a dependent ClassMolder.
     */
    void addDependent(final ClassMolder dep) {
        if (_dependent == null) {
            _dependent = new Vector<ClassMolder>();
        }
        _dependent.add(dep);
    }

    /**
     * Mutator method to set the extends ClassMolder.
     */
    void setExtends(final ClassMolder ext) {
        _extends = ext;
        ext.addExtendent(this);
    }

    /**
     * Mutator method to set the depends ClassMolder.
     */
    void setDepends(final ClassMolder dep) {
        _depends = dep;
        dep.addDependent(this);
    }

    public String toString() {
        return "ClassMolder " + _name;
    }

    /**
     * Return true if a key generator is used for the base type of this ClassMolder.
     */
    public boolean isKeyGenUsed() {
        return _isKeyGenUsed;
    }

    /**
     * Return true if a key generator is used for the base type of this ClassMolder.
     */
    public boolean isKeyGeneratorUsed() {
        return _isKeyGenUsed || (_extends != null && _extends.isKeyGeneratorUsed());
    }

    /**
     * Inspect the fields stored in the object passed as an argument for
     * contained objects.  Request an expireCache for each contained object.
     *
     * @param tx The {@link org.castor.persist.TransactionContext}
     * @param locker The object that contains the fields to be inspected
     */
    public void expireCache(final TransactionContext tx, final ObjectLock locker)
    throws PersistenceException {
        // get field values from cache
        Object[] fields = locker.getObject();

        if (fields == null) {
            return;
        }

        // iterate through all the field values and expire contained objects
        for (int i = 0; i < _fhs.length; i++) {
            _resolvers[i].expireCache(tx, fields[i]);
        }
    }
    
    public void resetResolvers () {
        if (!_resolversHaveBeenReset) {
            for (int i = 0; i < _fhs.length; i++) {
                _resolvers[i] = ResolverFactory.createRelationResolver (_fhs[i], this, i);
            }
            
            _resolversHaveBeenReset = true;
        }
    }

    /**
     * Returns the actual (OQL) statement for the specified named query.
     * 
     * @param name Named query name.
     * @return The actual (OQL) statement.
     */
    public String getNamedQuery(final String name) {
        return new ClassDescriptorJDONature(_clsDesc).getNamedQueries().get(name);
    }

    /**
     * Returns the actual (SQL) statement for the specified named native query.
     * 
     * @param name Named query name.
     * @return The actual (SQL) statement.
     */
    public NamedNativeQuery getNamedNativeQuery(final String name) {
        return new ClassDescriptorJDONature(_clsDesc).getNamedNativeQueries().get(name);
    }
}
