Package org.jpox.metadata

Source Code of org.jpox.metadata.AbstractMemberMetaData

/**********************************************************************
Copyright (c) 2004 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
2007 Xuan Baldauf - Implement solution for issue CORE-3272
2007 Xuan Baldauf - allow users to explictly state that an array whose component type is not PC may still have PC elements. See CORE-3274
    ...
**********************************************************************/
package org.jpox.metadata;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jpox.ClassLoaderResolver;
import org.jpox.ClassNameConstants;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.ClassNotResolvedException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.sco.SCOUtils;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.JavaUtils;
import org.jpox.util.StringUtils;

/**
* Abstract representation of MetaData for a field/property of a class/interface.
* The term "member" is used to represent either a field or a method(property). The term
* property is used to represent the name after cutting off any Java-beans style "get" prefix.
* This class is extended for fields (FieldMetaData) and properties (PropertyMetaData) to provide
* the explicit support for those components.
*
* @version $Revision: 1.20 $
*/
public abstract class AbstractMemberMetaData extends MetaData  implements Comparable, ColumnMetaDataContainer
{
    /** Contains the metadata for column(s). */
    protected ColumnMetaData[] columnMetaData;

    /** Meta-Data of any container. */
    protected ContainerMetaData container;

    /** EmbeddedMetaData. */
    protected EmbeddedMetaData embeddedMetaData;

    /** JoinMetaData. */
    protected JoinMetaData joinMetaData;

    /** ElementMetaData. */
    protected ElementMetaData elementMetaData;

    /** KeyMetaData. */
    protected KeyMetaData keyMetaData;

    /** ValueMetaData. */
    protected ValueMetaData valueMetaData;

    /** IndexMetaData. */
    protected IndexMetaData indexMetaData;

    /** The indexing value */
    protected IndexedValue indexed=null;

    /** UniqueMetaData. */
    protected UniqueMetaData uniqueMetaData;

    /** Whether to add a unique constraint. */
    protected final boolean uniqueConstraint;

    /** OrderMetaData. */
    protected OrderMetaData orderMetaData;

    /** ForeignKeyMetaData. */
    protected ForeignKeyMetaData foreignKeyMetaData;

    /** default-fetch-group tag value. */
    protected Boolean defaultFetchGroup;

    /** column tag value.  */
    protected String column;

    /** mapped-by tag value. */
    protected String mappedBy;

    /** embedded tag value. */
    protected Boolean embedded;

    /** Whether this field contains a reference that should be deleted when deleting this field.  */
    protected Boolean dependent;

    /** serialized tag value. */
    protected Boolean serialized;

    /** Whether to persist this relation when persisting the owning object. */
    protected boolean cascadePersist;

    /** Whether to update this relation when updating the owning object. */
    protected boolean cascadeUpdate;

    /** Whether to delete this relation when deleting the owning object (JPA). TODO Link this to dependent */
    protected boolean cascadeDelete;

    /** Whether to refresh this relation when refreshing the owning object (JPA). */
    protected boolean cascadeRefresh;

    /** load-fetch-group value. */
    protected String loadFetchGroup;

    /** Default recursion-depth according to proposed final draft spec, [12.7.2]. */
    public static final int DEFAULT_RECURSION_DEPTH = 1;

    /** Indicates the recursion-depth is not defined.  Use default value. */
    public static final int UNDEFINED_RECURSION_DEPTH = 0;

    /** recursion-depth value. */
    protected int recursionDepth = UNDEFINED_RECURSION_DEPTH;

    /** Field name. */
    protected final String name;

    /** null-value tag value (default is NONE). */
    protected NullValue nullValue=NullValue.NONE;

    /** persistence-modifier tag value. */
    protected FieldPersistenceModifier persistenceModifier=FieldPersistenceModifier.DEFAULT;

    /** primary key tag value. */
    protected Boolean primaryKey;

    /** Table name for this field. */
    protected String table;

    /** Catalog for the table specified for this field. */
    protected String catalog;

    /** Schema for the table specified for this field. */
    protected String schema;

    /**
     * The value-strategy attribute specifies the strategy used to generate
     * values for the field. This attribute has the same values and meaning as
     * the strategy attribute in datastoreidentity.
     */
    protected IdentityStrategy valueStrategy;

    /** Name of a value generator if the user wants to override the default JPOX generator. */
    protected String valueGeneratorName;

    /**
     * If the value-strategy is sequence, the sequence attribute specifies the
     * name of the sequence to use to automatically generate a value for the field.
     */
    protected String sequence;

    /**
     * Name of the class to which this field really belongs. Will be null if the field belongs
     * to the parent ClassMetaData, and will have a value if it is an overriding field.
     */
    protected String className = null;

    /**
     * Specification of the possible type(s) that can be stored in this field. This is for the case where the
     * field/property is declared as an interface, or Object and hence can contain derived types. This
     * provides the restriction to a particular type.
     */
    protected String[] fieldTypes;

    /** Field type being represented by this MetaData. */
    protected Class type;

    /** Field modifiers */
    protected int modifiers;

    /**
     * Id of the field in its class (only for fields managed by JDO).
     * If the value is -1, the field is NOT managed by JDO or the object hasn't
     * been populated.
     */
    protected int fieldId=-1;

    /** The relation type of this field (1-1, 1-N, M-N, N-1). */
    protected int relationType = -1;

    /**
     * MetaData for the other end of a relation when this member is a bidirectional relation.
     * This may be multiple fields if the FK is shared.
     */
    protected AbstractMemberMetaData[] relatedMemberMetaData = null;

    /** Temporary flag to signify if the field is ordered. */
    protected boolean ordered = false;

    // -------------------------------------------------------------------------
    // These fields are only used when the MetaData is read by the parser and
    // elements are dynamically added to the other elements. At runtime, "columnMetaData"
    // should be used.

    /**
     * Columns ColumnMetaData
     */
    protected List columns = new ArrayList();

    /** Name of the target entity (when used with JPA MetaData on OneToOne, OneToMany etc) */
    protected String targetClassName = null;

    /** Wrapper for the ugly JPA "lob" so that when being populated we should make this serialised in some way. */
    protected boolean storeInLob = false;

    /** JDO Flags for use in enhancement process - see JDO spec 21.14. */
    protected byte jdoFieldFlag;

    /**
     * Convenience constructor taking defaults
     * @param parent Parent component
     * @param name Name of the field
     */
    public AbstractMemberMetaData(MetaData parent, final String name)
    {
        this(parent, name, null, null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null, null, null, null);
    }

    /**
     * Convenience constructor to copy the specification from the passed field.
     * This is used when we have an overriding field and we make a copy of the baseline
     * field as a starting point.
     * @param parent The parent
     * @param fmd The field to copy
     */
    public AbstractMemberMetaData(MetaData parent, AbstractMemberMetaData fmd)
    {
        super(parent);

        // Copy the simple field values
        this.name = fmd.name;
        this.primaryKey = fmd.primaryKey;
        this.defaultFetchGroup = fmd.defaultFetchGroup;
        this.column = fmd.column;
        this.mappedBy = fmd.mappedBy;
        this.dependent = fmd.dependent;
        this.embedded = fmd.embedded;
        this.serialized = fmd.serialized;
        this.cascadePersist = fmd.cascadePersist;
        this.cascadeUpdate = fmd.cascadeUpdate;
        this.cascadeDelete = fmd.cascadeDelete;
        this.cascadeRefresh = fmd.cascadeRefresh;
        this.nullValue = fmd.nullValue;
        this.persistenceModifier = fmd.persistenceModifier;
        this.table = fmd.table;
        this.indexed = fmd.indexed;
        this.valueStrategy = fmd.valueStrategy;
        this.valueGeneratorName = fmd.valueGeneratorName;
        this.sequence = fmd.sequence;
        this.uniqueConstraint = fmd.uniqueConstraint;
        this.loadFetchGroup = fmd.loadFetchGroup;
        if (fmd.fieldTypes != null)
        {
            this.fieldTypes = new String[fmd.fieldTypes.length];
            for (int i=0;i<fmd.fieldTypes.length;i++)
            {
                this.fieldTypes[i] = fmd.fieldTypes[i];
            }
        }

        // Create copies of the object fields
        if (fmd.joinMetaData != null)
        {
            this.joinMetaData = new JoinMetaData(this, fmd.joinMetaData);
        }
        if (fmd.elementMetaData != null)
        {
            this.elementMetaData = new ElementMetaData(this, fmd.elementMetaData);
        }
        if (fmd.keyMetaData != null)
        {
            this.keyMetaData = new KeyMetaData(this, fmd.keyMetaData);
        }
        if (fmd.valueMetaData != null)
        {
            this.valueMetaData = new ValueMetaData(this, fmd.valueMetaData);
        }
        if (fmd.orderMetaData != null)
        {
            this.orderMetaData = new OrderMetaData(this, fmd.orderMetaData);
        }
        if (fmd.indexMetaData != null)
        {
            this.indexMetaData = new IndexMetaData(fmd.indexMetaData);
        }
        if (fmd.uniqueMetaData != null)
        {
            this.uniqueMetaData = new UniqueMetaData(fmd.uniqueMetaData);
        }
        if (fmd.foreignKeyMetaData != null)
        {
            this.foreignKeyMetaData = new ForeignKeyMetaData(fmd.foreignKeyMetaData);
        }
        if (fmd.embeddedMetaData != null)
        {
            this.embeddedMetaData = new EmbeddedMetaData(this, fmd.embeddedMetaData);
        }
        if (fmd.container != null)
        {
            if (fmd.container instanceof CollectionMetaData)
            {
                container = new CollectionMetaData(this, (CollectionMetaData)fmd.container);
            }
            else if (fmd.container instanceof MapMetaData)
            {
                container = new MapMetaData(this, (MapMetaData)fmd.container);
            }
            else if (fmd.container instanceof ArrayMetaData)
            {
                container = new ArrayMetaData(this, (ArrayMetaData)fmd.container);
            }
        }
        for (int i=0;i<fmd.columns.size();i++)
        {
            addColumn(new ColumnMetaData(this,(ColumnMetaData)fmd.columns.get(i)));
        }
    }

    /**
     * Constructor. Saves the MetaData with the specified values. The object is
     * then in an "unpopulated" state. It can become "populated" by calling the
     * <B>populate()</B> method which compares it against the field it is to
     * represent and updates any unset attributes and flags up any errors.
     * @param parent parent MetaData instance
     * @param name field name
     * @param pk attribute primary-key value
     * @param modifier attribute persistence-modifier value
     * @param defaultFetchGroup attribute default-fetch-group value
     * @param nullValue attribute null-value value
     * @param embedded attribute embedded value
     * @param serialized attribute serialized value
     * @param dependent attribute dependent value
     * @param mappedBy attribute mapped-by value
     * @param column attribute column value
     * @param table attribute table value
     * @param catalog attribute catalog value
     * @param schema attribute schema value
     * @param deleteAction attribute delete-action value
     * @param indexed Whether this is indexed
     * @param unique Apply a unique constraint
     * @param recursionDepth The depth of fetch to use when recursing
     * @param loadFetchGroup Name of the additional fetch group to use when loading
     * @param valueStrategy attribute value-strategy value
     * @param sequence attribute sequence value
     * @param fieldType Implementation type(s) for field.
     */
    public AbstractMemberMetaData(MetaData parent,
                         final String name,
                         final String pk,
                         final String modifier,
                         final String defaultFetchGroup,
                         final String nullValue,
                         final String embedded,
                         final String serialized,
                         final String dependent,
                         final String mappedBy,
                         final String column,
                         final String table,
                         final String catalog,
                         final String schema,
                         final String deleteAction,
                         final String indexed,
                         final String unique,
                         final String recursionDepth,
                         final String loadFetchGroup,
                         final String valueStrategy,
                         final String sequence,
                         final String fieldType)
    {
        super(parent);

        if (name == null)
        {
            throw new JPOXUserException(LOCALISER.msg("044041","name", getClassName(true), "field"));
        }
        if (name.indexOf('.') >= 0)
        {
            // TODO Check if this is a valid className
            this.className = name.substring(0,name.lastIndexOf('.'));
            this.name = name.substring(name.lastIndexOf('.')+1);
        }
        else
        {
            this.name = name;
        }

        // "primary-key"
        if (!StringUtils.isWhitespace(pk))
        {
            if (pk.equalsIgnoreCase("true"))
            {
                this.primaryKey = Boolean.TRUE;
                this.defaultFetchGroup = Boolean.TRUE;
            }
            else
            {
                this.primaryKey = Boolean.FALSE;
            }
        }

        this.column = (StringUtils.isWhitespace(column) ? null : column);
        this.mappedBy = (StringUtils.isWhitespace(mappedBy) ? null : mappedBy);

        // "dependent"
        if (dependent != null)
        {
            if (dependent.equalsIgnoreCase("true"))
            {
                this.dependent = Boolean.TRUE;
            }
            else if (dependent.equalsIgnoreCase("false"))
            {
                this.dependent = Boolean.FALSE;
            }
        }

        // "embedded"
        if (embedded != null)
        {
            if (embedded.equalsIgnoreCase("true"))
            {
                this.embedded = Boolean.TRUE;
            }
            else if (embedded.equalsIgnoreCase("false"))
            {
                this.embedded = Boolean.FALSE;
            }
        }

        // "serialized"
        if (serialized != null)
        {
            if (serialized.equalsIgnoreCase("true"))
            {
                this.serialized = Boolean.TRUE;
            }
            else if (serialized.equalsIgnoreCase("false"))
            {
                this.serialized = Boolean.FALSE;
            }
        }

        // "null-value"
        this.nullValue = NullValue.getNullValue(nullValue);

        // "persistence-modifier"
        if (!StringUtils.isWhitespace(modifier))
        {
            persistenceModifier = FieldPersistenceModifier.getFieldPersistenceModifier(modifier);
        }
        if (persistenceModifier == null)
        {
            JPOXLogger.METADATA.error(LOCALISER.msg("044117",name,getClassName(false),modifier,"persistence-modifier"));
            throw new InvalidMetaDataException(LOCALISER,
                                               "044117",
                                               name,getClassName(false),
                                               modifier,
                                               "persistence-modifier");           
        }
       
        // "default-fetch-group"
        if (defaultFetchGroup != null)
        {
            if (defaultFetchGroup.equalsIgnoreCase("true"))
            {
                this.defaultFetchGroup = Boolean.TRUE;
            }
            else if (defaultFetchGroup.equalsIgnoreCase("false"))
            {
                this.defaultFetchGroup = Boolean.FALSE;
            }
        }

        // "table"
        this.table = (StringUtils.isWhitespace(table) ? null : table);
        this.catalog = (StringUtils.isWhitespace(catalog) ? null : catalog);
        this.schema = (StringUtils.isWhitespace(schema) ? null : schema);

        if (deleteAction != null)
        {
            foreignKeyMetaData = new ForeignKeyMetaData(null, null, null, null, deleteAction, null);
        }

        this.indexed = IndexedValue.getIndexedValue(indexed);

        if (unique != null && unique.equalsIgnoreCase("true"))
        {
            uniqueConstraint = true;
        }
        else
        {
            uniqueConstraint = false;
        }

        // Fetch Group
        this.loadFetchGroup = (StringUtils.isWhitespace(loadFetchGroup) ? null : loadFetchGroup);
        if (recursionDepth != null)
        {
            try
            {
                this.recursionDepth = (new Integer(recursionDepth)).intValue();
            }
            catch (Exception e)
            {
                // Not parseable so set as "undefined"
                this.recursionDepth = UNDEFINED_RECURSION_DEPTH;
            }
        }

        this.valueStrategy = valueStrategy == null ? null : IdentityStrategy.getIdentityStrategy(valueStrategy);
        this.sequence = (StringUtils.isWhitespace(sequence) ? null : sequence);
        if (!StringUtils.isWhitespace(fieldType))
        {
            // Split the passed value assuming that it is comma-separated
            this.fieldTypes = MetaDataUtils.getInstance().getValuesForCommaSeparatedAttribute(fieldType);
        }

        if (getMetaDataManager() != null)
        {
            // Set defaults for cascading
            // If we have no MetaDataManager here then we are likely a field for an Index/Unique etc so dont need this
            ApiAdapter apiAdapter = getMetaDataManager().getOMFContext().getApiAdapter();
            this.cascadePersist = apiAdapter.getDefaultCascadePersistForField();
            this.cascadeUpdate = apiAdapter.getDefaultCascadeUpdateForField();
            this.cascadeDelete = apiAdapter.getDefaultCascadeDeleteForField();
            this.cascadeRefresh = apiAdapter.getDefaultCascadeRefreshForField();
        }
    }

    /**
     * Method to provide the details of the field being represented by this MetaData hence populating
     * certain parts of the MetaData. This is used to firstly provide defaults for attributes that aren't
     * specified in the MetaData, and secondly to report any errors with attributes that have been specifed
     * that are inconsistent with the field being represented.
     * Either a field or a method should be passed in (one or the other) depending on what is being represented
     * by this "member".
     * @param clr ClassLoaderResolver to use for any class loading
     * @param field Field that we are representing (if it's a field)
     * @param method Method(property) that we are representing (if it's a method).
     * @param primary the primary ClassLoader to use (or null)
     */
    public synchronized void populate(ClassLoaderResolver clr, Field field, Method method, ClassLoader primary)
    {
        if (isPopulated() || isInitialised())
        {
            return;
            /*JPOXLogger.METADATA.error(LOCALISER.msg("044107",name,getClassName(false)));
            throw new InvalidMetaDataException(LOCALISER,
                "044107",
                name,getClassName(false));*/
        }

        if (field == null && method == null)
        {
            JPOXLogger.METADATA.error(LOCALISER.msg("044106",name,getClassName(false)));
            throw new InvalidMetaDataException(LOCALISER,
                "MetaData.Field.PopulateWithNullError",
                name,getClassName(false));
        }

        // No class loader, so use System
        if (clr == null)
        {
            JPOXLogger.METADATA.warn(LOCALISER.msg("044067",name,getClassName(true)));
            clr = getAbstractClassMetaData().getMetaDataManager().getOMFContext().getClassLoaderResolver(null);
        }

        if (field != null)
        {
          this.type = field.getType();
          this.modifiers = field.getModifiers();
        }
        else if (method != null)
        {
          this.type = method.getReturnType();
          this.modifiers = method.getModifiers();
        }
       
        if (className != null)
        {
            // If the field is overriding a superclass field, check that it is valid
            Class thisClass = null;
            try
            {
                thisClass = clr.classForName(getAbstractClassMetaData().getPackageName() + "." + getAbstractClassMetaData().getName());
            }
            catch (ClassNotResolvedException cnre)
            {
                // Do nothing
            }
           
            Class fieldClass = null;
            try
            {
                fieldClass = clr.classForName(className);
            }
            catch (ClassNotResolvedException cnre)
            {
                try
                {
                    fieldClass = clr.classForName(getAbstractClassMetaData().getPackageName() + "." + className);
                    className = getAbstractClassMetaData().getPackageName() + "." + className;
                }
                catch (ClassNotResolvedException cnre2)
                {
                    JPOXLogger.METADATA.error(LOCALISER.msg("044113", getAbstractClassMetaData().getName(), name, className));
                    throw new InvalidMetaDataException(LOCALISER, "044113", getAbstractClassMetaData().getName(), name, className);
                }
            }
            if (fieldClass != null && !fieldClass.isAssignableFrom(thisClass))
            {
                // TODO We could also check if PersistenceCapable, but won't work when enhancing
                JPOXLogger.METADATA.error(LOCALISER.msg("044114", getAbstractClassMetaData().getName(), name, className));
                throw new InvalidMetaDataException(LOCALISER, "044114", getAbstractClassMetaData().getName(), name, className);
            }
        }

        if (primaryKey == null)
        {
            // Primary key not set by user so initialise it to false
            primaryKey = Boolean.FALSE;
        }

        // Update "embedded" based on type
        if (primaryKey == Boolean.FALSE && embedded == null)
        {
            Class element_type=getType();
            if (element_type.isArray())
            {
                element_type = element_type.getComponentType();
                if (getMetaDataManager().getOMFContext().getTypeManager().isDefaultEmbeddedType(element_type))
                {
                    embedded = Boolean.TRUE;
                }
            }
            else if (getMetaDataManager().getOMFContext().getTypeManager().isDefaultEmbeddedType(element_type))
            {
                embedded = Boolean.TRUE;
            }
        }
        if (embedded == null)
        {
            embedded = Boolean.FALSE;
        }

        // Update "persistence-modifier" according to type etc
        if (FieldPersistenceModifier.DEFAULT.equals(persistenceModifier))
        {
            boolean isPcClass = getType().isArray() ? isFieldArrayTypePersistable() : isFieldTypePersistable();
            if (!isPcClass)
            {
                if (getType().isArray() && getType().getComponentType().isInterface())
                {
                    isPcClass =
                        (getAbstractClassMetaData().getMetaDataManager().getMetaDataForClassInternal(getType().getComponentType(), clr) != null);
                }
                else if (getType().isInterface())
                {
                    isPcClass = (getAbstractClassMetaData().getMetaDataManager().getMetaDataForClassInternal(getType(), clr) != null);
                }
            }

            persistenceModifier =
                getMetaDataManager().getOMFContext().getTypeManager().getDefaultFieldPersistenceModifier(
                    getType(),modifiers,isPcClass);
        }

        // TODO If this field is NONE in superclass, make it NONE here too

        // Check for array types supported by JPOX
        if (!persistenceModifier.equals(FieldPersistenceModifier.NONE) && getType().isArray() &&
            !getAbstractClassMetaData().getMetaDataManager().isEnhancing())
        {
            // JPOX supports simple arrays, arrays of PCs, and arrays of reference types, and anything serialised
            ApiAdapter api = getMetaDataManager().getOMFContext().getApiAdapter();
            if (!getMetaDataManager().getOMFContext().getTypeManager().isSupportedType(getType().getName()) &&
                !api.isPersistable(getType().getComponentType()) &&
                !getType().getComponentType().isInterface() &&
                !getType().getComponentType().getName().equals("java.lang.Object")
                && serialized != Boolean.TRUE)
            {
                JPOXLogger.METADATA.warn(LOCALISER.msg("044111",name,getClassName(false)));
                throw new InvalidMetaDataException(LOCALISER,
                    "044111", name,getClassName(false));
            }
        }

        // Update "default-fetch-group" according to type
        if (defaultFetchGroup == null && persistenceModifier.equals(FieldPersistenceModifier.NONE))
        {
            defaultFetchGroup = Boolean.FALSE;
        }
        else if (defaultFetchGroup == null && persistenceModifier.equals(FieldPersistenceModifier.TRANSACTIONAL))
        {
            defaultFetchGroup = Boolean.FALSE;
        }
        else if (defaultFetchGroup == null)
        {
            defaultFetchGroup = Boolean.FALSE;
            if (!primaryKey.equals(Boolean.TRUE) &&
                    getMetaDataManager().getOMFContext().getTypeManager().isDefaultFetchGroup(getType()))
            {
                defaultFetchGroup = Boolean.TRUE;
            }
        }

        // Field is not specified as "persistent" yet has DFG or primary-key !
        if (persistenceModifier.equals(FieldPersistenceModifier.TRANSACTIONAL) ||
            persistenceModifier.equals(FieldPersistenceModifier.NONE))
        {
            if (defaultFetchGroup == Boolean.TRUE || primaryKey == Boolean.TRUE)
            {
                throw new InvalidMetaDataException(LOCALISER,
                    "044109",
                    name, getClassName(true), this.getType().getName(),
                    persistenceModifier.toString());
            }
        }

        if (storeInLob)
        {
            // Set up the serialized flag according to the field type in line with JPA1
            boolean useClob = false;
            if (type == String.class ||
                (type.isArray() && type.getComponentType() == Character.class) ||
                (type.isArray() && type.getComponentType() == char.class))
            {
                useClob = true;
                if (columns == null || columns.size() == 0)
                {
                    // Create a CLOB column. What if the RDBMS doesnt support CLOB ?
                    addColumn(new ColumnMetaData(this, column, null, null, "CLOB",
                        null, null, null, null, null, null, null, null, null));
                }
                else
                {
                    ColumnMetaData colmd = (ColumnMetaData)columns.get(0);
                    colmd.setJdbcType("CLOB");
                }
            }
            if (!useClob)
            {
                serialized = Boolean.TRUE;
            }
        }

        if (container == null && type.isArray())
        {
            // User hasnt specifed <array> but the field is an array so infer it
            Class arrayCls = type.getComponentType();
            ArrayMetaData arrmd = new ArrayMetaData(this, arrayCls.getName(), null, null, null);
            container = arrmd;
        }

        if (container == null && targetClassName != null)
        {
            // User has specified target class name (JPA) so generate the <collection> or <map> accordingly
            if (java.util.Collection.class.isAssignableFrom(type))
            {
                CollectionMetaData colmd = new CollectionMetaData(this, targetClassName, null, null, null);
                container = colmd;
            }
            else if (java.util.Map.class.isAssignableFrom(type))
            {
                MapMetaData mapmd = new MapMetaData(this, null, null, null, null, targetClassName, null, null, null);
                container = mapmd;
            }
        }

        if (JavaUtils.isJRE1_5OrAbove())
        {
            // User hasnt specified collection/map but using JDK1.5 so construct the <collection>, <map> if using generics
            if (java.util.Collection.class.isAssignableFrom(type))
            {
                String elementType = null;
                if (field != null)
                {
                    elementType = ClassUtils.getCollectionElementType(field);
                }
                else
                {
                    elementType = ClassUtils.getCollectionElementType(method);
                }
                if (elementType != null)
                {
                    // Use generics information to infer any missing parts
                    if (container == null)
                    {
                        // Create <collection element-type="...">
                        CollectionMetaData colmd = new CollectionMetaData(this, elementType, null, null, null);
                        container = colmd;
                    }
                    else if (getCollection().element.type == null ||
                        getCollection().element.type.equals(ClassNameConstants.Object))
                    {
                        // element-type not set so update element-type
                        getCollection().element.type = elementType;
                    }
                }
            }
            else if (java.util.Map.class.isAssignableFrom(type))
            {
                String keyType = null;
                String valueType = null;
                if (field != null)
                {
                    keyType = ClassUtils.getMapKeyType(field);
                }
                else
                {
                    keyType = ClassUtils.getMapKeyType(method);
                }
                if (field != null)
                {
                    valueType = ClassUtils.getMapValueType(field);
                }
                else
                {
                    valueType = ClassUtils.getMapValueType(method);
                }
                if (keyType != null && valueType != null)
                {
                    // Use generics information to infer any missing parts
                    if (container == null)
                    {
                        // Create <map key-type="..." value-type="...">
                        MapMetaData mapmd = new MapMetaData(this, keyType, null, null, null, valueType, null, null, null);
                        container = mapmd;
                    }
                    else
                    {
                        if (getMap().key.type == null || getMap().key.type.equals(ClassNameConstants.Object))
                        {
                            // key-type not set so update key-type
                            getMap().key.type = keyType;
                        }
                        if (getMap().value.type == null || getMap().value.type.equals(ClassNameConstants.Object))
                        {
                            // value-type not set so update value-type
                            getMap().value.type = valueType;
                        }
                    }
                }
            }
        }

        if (hasCollection() && ordered && orderMetaData == null)
        {
            setOrderMetaData(new OrderMetaData("#PK")); // Special value recognised by OrderMetaData
        }

        if (elementMetaData == null && !isSerialized() && !isEmbedded() && columnMetaData != null)
        {
            if (hasCollection() || hasArray())
            {
                // Collection/Array with column(s) specified on field but not serialising so move to element
                ElementMetaData elemmd = new ElementMetaData(this, null, null, null, null, null, null);
                setElementMetaData(elemmd);
                for (int i=0;i<columnMetaData.length;i++)
                {
                    elemmd.addColumn(columnMetaData[i]);
                }
            }
        }
        if (valueMetaData == null && hasMap() && !isEmbedded() && !isSerialized() && columnMetaData != null)
        {
            // Column specified directly but no value and not serialising field so add a value and apply cols there
            ValueMetaData valmd = new ValueMetaData(this, null, null, null, null, null, null);
            setValueMetaData(valmd);
            for (int i=0;i<columnMetaData.length;i++)
            {
                valmd.addColumn(columnMetaData[i]);
            }
        }

        if (this.container != null && this.dependent != null)
        {
            // Check for invalid dependent field specifications
            JPOXLogger.METADATA.error(LOCALISER.msg("044110",
                name, getClassName(false), ((ClassMetaData)this.parent).getName()));           
            throw new InvalidMetaDataException(LOCALISER,
                "044110",
                name, getClassName(false), ((ClassMetaData)this.parent).getName());
        }

        if (elementMetaData != null)
        {
            // Populate any element object
            elementMetaData.populate(clr, primary);
        }
        if (keyMetaData != null)
        {
            // Populate any key object
            keyMetaData.populate(clr, primary);
        }
        if (valueMetaData != null)
        {
            // Populate any value object
            valueMetaData.populate(clr, primary);
        }
        if (embeddedMetaData != null)
        {
            // Populate any embedded object
            embeddedMetaData.populate(clr, primary);
            embedded = Boolean.TRUE;
        }

        if (elementMetaData != null && elementMetaData.mappedBy != null && mappedBy == null)
        {
            // User has specified "mapped-by" on element instead of field so pull it up to this level
            // <element mapped-by="..."> is the same as <field mapped-by="..."> for a collection field
            mappedBy = elementMetaData.mappedBy;
        }

        if (container != null && persistenceModifier == FieldPersistenceModifier.PERSISTENT)
        {
            // Populate any container
            if (container instanceof CollectionMetaData)
            {
                if (cascadeDelete)
                {
                    // User has set cascade-delete (JPA) so set the element as dependent
                    getCollection().element.dependent = Boolean.TRUE;
                }
                getCollection().populate(clr, primary);
            }
            else if (container instanceof MapMetaData)
            {
                if (cascadeDelete)
                {
                    // User has set cascade-delete (JPA) so set the value as dependent
                    getMap().key.dependent = Boolean.TRUE; // Not really necessary but JPA doesnt provide for PC key fields specification
                    getMap().value.dependent = Boolean.TRUE;
                }
                getMap().populate(clr, primary);
            }
            else if (container instanceof ArrayMetaData)
            {
                if (cascadeDelete)
                {
                    // User has set cascade-delete (JPA) so set the element as dependent
                    getArray().element.dependent = Boolean.TRUE;
                }
                getArray().populate(clr, primary);
            }
        }
        if (isFieldTypePersistable() && cascadeDelete)
        {
            setDependent(true);
        }

        if (hasExtension("implementation-classes"))
        {
            // Check the validity of the implementation-classes and qualify them where required.
            StringBuffer str = new StringBuffer();
            String[] implTypes = getValuesForExtension("implementation-classes");
            for (int i=0;i<implTypes.length;i++)
            {
                String implTypeName = ClassUtils.createFullClassName(getAbstractClassMetaData().getPackageName(), implTypes[i]);
                if (i > 0)
                {
                    str.append(",");
                }

                try
                {
                    clr.classForName(implTypeName);
                    str.append(implTypeName);
                }
                catch (ClassNotResolvedException cnre)
                {
                    try
                    {
                        // Maybe the user specified a java.lang class without fully-qualifying it
                        // This is beyond the scope of the JDO spec which expects java.lang cases to be fully-qualified
                        String langClassName = ClassUtils.getJavaLangClassForType(implTypeName);
                        clr.classForName(langClassName);
                        str.append(langClassName);
                    }
                    catch (ClassNotResolvedException cnre2)
                    {
                        // Implementation type not found
                        throw new InvalidMetaDataException(LOCALISER, "044116",
                            name, getClassName(false), implTypes[i]);
                    }
                }
            }
            addExtension(JPOX_VENDOR_NAME, "implementation-classes", str.toString()); // Replace with this new value
        }

        // Set up JDO flags for enhancement process
        byte serializable = 0;
        if (Serializable.class.isAssignableFrom(getType()) || getType().isPrimitive())
        {
            serializable = PersistenceFlags.SERIALIZABLE;
        }

        if (FieldPersistenceModifier.NONE.equals(persistenceModifier))
        {
            jdoFieldFlag = 0;
        }
        else if (FieldPersistenceModifier.TRANSACTIONAL.equals(persistenceModifier) &&
                 Modifier.isTransient(modifiers))
        {
            jdoFieldFlag = (byte) (PersistenceFlags.CHECK_WRITE | serializable);
        }
        else if (primaryKey.booleanValue())
        {
            jdoFieldFlag = (byte) (PersistenceFlags.MEDIATE_WRITE | serializable);
        }
        else if (defaultFetchGroup.booleanValue())
        {
            jdoFieldFlag = (byte) (PersistenceFlags.CHECK_READ | PersistenceFlags.CHECK_WRITE | serializable);
        }
        else if (!defaultFetchGroup.booleanValue())
        {
            jdoFieldFlag = (byte) (PersistenceFlags.MEDIATE_READ | PersistenceFlags.MEDIATE_WRITE | serializable);
        }
        else
        {
            jdoFieldFlag = 0;
        }

        setPopulated();
    }

    /**
     * Initialisation method. This should be called AFTER using the populate
     * method if you are going to use populate. It creates the internal
     * convenience arrays etc needed for normal operation.
     */
    public synchronized void initialise()
    {
        if (persistenceModifier == FieldPersistenceModifier.NONE)
        {
            setInitialised();
            return;
        }

        // Cater for user specifying column name, or columns
        if (columns.size() == 0 && column != null)
        {
            columnMetaData = new ColumnMetaData[1];
            columnMetaData[0] = new ColumnMetaData(this, column);
            columnMetaData[0].initialise();
        }
        else if (columns.size() == 1 && column != null)
        {
            // Cater for user specifying <column> and <field column="...">
            columnMetaData = new ColumnMetaData[1];
            columnMetaData[0] = (ColumnMetaData) columns.get(0);
            if (columnMetaData[0].getName() == null)
            {
                columnMetaData[0].setName(column);
            }
            columnMetaData[0].initialise();
        }
        else
        {
            columnMetaData = new ColumnMetaData[columns.size()];
            for (int i=0; i<columnMetaData.length; i++)
            {
                columnMetaData[i] = (ColumnMetaData) columns.get(i);
                columnMetaData[i].initialise();
            }
        }

        // Initialise all sub-objects
        if (container != null)
        {
            container.initialise();
        }
        if (embeddedMetaData != null)
        {
            embeddedMetaData.initialise();
        }
        if (joinMetaData != null)
        {
            joinMetaData.initialise();
        }
        if (elementMetaData != null)
        {
            elementMetaData.initialise();
        }
        if (keyMetaData != null)
        {
            keyMetaData.initialise();
        }
        if (valueMetaData != null)
        {
            valueMetaData.initialise();
        }

        // Interpret the "indexed" value to create our IndexMetaData where it wasn't specified that way
        if (indexMetaData == null && columnMetaData != null && indexed != null && indexed != IndexedValue.FALSE)
        {
            indexMetaData = new IndexMetaData(null, null, (indexed == IndexedValue.UNIQUE) ? "true" : "false");
            for (int i=0;i<columnMetaData.length;i++)
            {
                indexMetaData.addColumn(columnMetaData[i]);
            }
        }
        if (indexMetaData != null)
        {
            indexMetaData.initialise();
        }
        if (uniqueMetaData == null && uniqueConstraint)
        {
            uniqueMetaData = new UniqueMetaData(null, column, null);
            for (int i=0;i<columnMetaData.length;i++)
            {
                uniqueMetaData.addColumn(columnMetaData[i]);
            }
        }
        if (uniqueMetaData != null)
        {
            uniqueMetaData.initialise();
        }

        if (foreignKeyMetaData != null)
        {
            foreignKeyMetaData.initialise();
        }
        if (orderMetaData != null)
        {
            orderMetaData.initialise();
        }

        if ((hasCollection() && SCOUtils.collectionHasSerialisedElements(this)) ||
            (hasMap() && SCOUtils.mapHasSerialisedKeysAndValues(this)) ||
            (hasArray() && SCOUtils.arrayIsStoredInSingleColumn(this)))
        {
            boolean setSerialised = true;
            if (hasArray() && MetaDataUtils.getInstance().arrayStorableAsByteArrayInSingleColumn(this))
            {
                // These can be streamed as byte arrays
                setSerialised = false;
            }

            // Collection/Array with serialised elements or Map with serialised keys and values so set as serialised for later
            if (setSerialised)
            {
                serialized = Boolean.TRUE;
            }
        }

        if (hasExtension("cascade-persist"))
        {
            // JDO doesnt have a metadata attribute for this so we use an extension
            String cascadeValue = getValueForExtension("cascade-persist");
            if (cascadeValue.equalsIgnoreCase("true"))
            {
                cascadePersist = true;
            }
            else if (cascadeValue.equalsIgnoreCase("false"))
            {
                cascadePersist = false;
            }
        }
        if (hasExtension("cascade-update"))
        {
            // JDO doesnt have a metadata attribute for this so we use an extension
            String cascadeValue = getValueForExtension("cascade-update");
            if (cascadeValue.equalsIgnoreCase("true"))
            {
                cascadeUpdate = true;
            }
            else if (cascadeValue.equalsIgnoreCase("false"))
            {
                cascadeUpdate = false;
            }
        }
        if (hasExtension("cascade-refresh"))
        {
            // JDO doesnt have a metadata attribute for this so we use an extension
            String cascadeValue = getValueForExtension("cascade-refresh");
            if (cascadeValue.equalsIgnoreCase("true"))
            {
                cascadeRefresh = true;
            }
            else if (cascadeValue.equalsIgnoreCase("false"))
            {
                cascadeRefresh = false;
            }
        }

        setInitialised();
    }

    /**
     * Utility to return if this field is persistable.
     * Not valid for use by the enhancer. Must be overridden for that mode.
     * @return Whether the field type is persistable.
     */
    public boolean isFieldTypePersistable()
    {
        if (getAbstractClassMetaData().getMetaDataManager().isEnhancing())
        {
            // Enhancing so return if we have MetaData that is persistable
            AbstractClassMetaData cmd =
                getAbstractClassMetaData().getMetaDataManager().readMetaDataForClass(type.getName());
            if (cmd != null && cmd instanceof ClassMetaData &&
                cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
            {
                return true;
            }
        }
        return getMetaDataManager().getOMFContext().getApiAdapter().isPersistable(type);
    }

    /**
     * Utility to return if this array field has elements that are Persistable.
     * Not valid for use by the enhancer. Must be overridden for that mode.
     * If the field is not an array will return false.
     * @return Whether the field type is persistable.
     */
    public boolean isFieldArrayTypePersistable()
    {
        if (!type.isArray())
        {
            return false;
        }
        if (getAbstractClassMetaData().getMetaDataManager().isEnhancing())
        {
            // Enhancing so return if we have MetaData that is PersistenceCapable
            AbstractClassMetaData cmd =
                getAbstractClassMetaData().getMetaDataManager().readMetaDataForClass(type.getComponentType().getName());
            if (cmd != null && cmd instanceof ClassMetaData &&
                cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
            {
                return true;
            }
        }
        return getMetaDataManager().getOMFContext().getApiAdapter().isPersistable(type.getComponentType());
    }

    /**
     * Convenience method to return if this field/property is static.
     * When the object is not "populated" always returns false;
     * @return Whether the field/property is static
     */
    public boolean isStatic()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isStatic(modifiers);
    }

    /**
     * Convenience method to return if this field/property is final.
     * When the object is not "populated" always returns false;
     * @return Whether the field is field/property
     */
    public boolean isFinal()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isFinal(modifiers);
    }

    /**
     * Convenience method to return if this field/property is transient.
     * When the object is not "populated" always returns false;
     * @return Whether the field/property is transient
     */
    public boolean isTransient()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isTransient(modifiers);
    }

    /**
     * Convenience method to return if this field/property is public.
     * When the object is not "populated" always returns false;
     * @return Whether the field/property is public
     */
    public boolean isPublic()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isPublic(modifiers);
    }

    /**
     * Convenience method to return if this field/property is protected.
     * When the object is not "populated" always returns false;
     * @return Whether the field/property is protected
     */
    public boolean isProtected()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isProtected(modifiers);
    }

    /**
     * Convenience method to return if this field/property is private.
     * When the object is not "populated" always returns false;
     * @return Whether the field/property is private
     */
    public boolean isPrivate()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isPrivate(modifiers);
    }

    /**
     * Convenience method to return if this field represents an abstract property.
     * @return Whether the property is abstract
     */
    public boolean isAbstract()
    {
        if (!isPopulated())
        {
            return false;
        }
        return Modifier.isAbstract(modifiers);
    }

    // ------------------------------- Accessors -------------------------------

    /**
     * The value-strategy attribute specifies the strategy used to generate
     * values for the field. This attribute has the same values and meaning as
     * the strategy attribute in datastoreidentity.
     * @return the value strategy
     */
    public IdentityStrategy getValueStrategy()
    {
        return valueStrategy;
    }

    /**
     * Name of a (user-provided) value generator to override the default JPOX generator for this strategy.
     * @return Name of user provided value generator
     */
    public String getValueGeneratorName()
    {
        return valueGeneratorName;
    }

    /**
     * If the value-strategy is sequence, the sequence attribute specifies the
     * name of the sequence to use to automatically generate a value for the
     * field.
     * @return the sequence name
     */
    public String getSequence()
    {
        return sequence;
    }

    /**
     * Accessor for the (additional) fetch group for loading this field
     * @return Returns the load fetch group
     */
    public String getLoadFetchGroup()
    {
        return loadFetchGroup;
    }

    /**
     * Convenience method to set the load fetch group if required after construction.
     * @param loadFetchGroup Name of the load fetch group
     */
    public void setLoadFetchGroup(String loadFetchGroup)
    {
        this.loadFetchGroup = loadFetchGroup;
    }

    /**
     * Accessor for the depth of the fetch when recursing
     * @return Returns the depth of the fetch when recursing
     */
    public int getRecursionDepth()
    {
        return recursionDepth;
    }

    /**
     * Convenience method to navigate back through the parents to find the overall
     * ClassMetaData handling this object. This is to cater specifically for nested
     * embedded fields where you can nest object several levels deep.
     * @param metadata The metadata to check
     * @return The overall class metadata for this element
     */
    protected MetaData getOverallParentClassMetaData(MetaData metadata)
    {
        if (metadata == null)
        {
            return null;
        }
        else if (metadata instanceof AbstractClassMetaData)
        {
            return metadata;
        }
        else
        {
            return getOverallParentClassMetaData(metadata.getParent());
        }
    }

    /**
     * Convenience accessor for the MetaData of the parent class.
     * @return Returns the MetaData of the parent class.
     */
    public AbstractClassMetaData getAbstractClassMetaData()
    {
        // TODO Consider replacing this method with the getOverallParentClassMetaData above since its generalised
        if (parent == null)
        {
            return null;
        }
        else if (parent instanceof AbstractClassMetaData)
        {
            return (AbstractClassMetaData)parent;
        }
        else if (parent instanceof EmbeddedMetaData)
        {
            // <embedded> is contained in a <field>, <element>, <key>, <value>
            // but could be multiple levels deep so adopt a generic strategy for finding the parent class
            MetaData parentMd = ((EmbeddedMetaData)parent).getParent();
            return (AbstractClassMetaData)getOverallParentClassMetaData(parentMd.getParent());
        }
        return null;
    }

    /**
     * Accessor for orderMetaData
     * @return Returns the orderMetaData.
     */
    public final OrderMetaData getOrderMetaData()
    {
        return orderMetaData;
    }
   
    /**
     * Accessor for the field name
     * @return field name
     */
    public String getName()
    {
        return name;
    }

    /**
     * Accessor for the full field name. This prepends the class name.
     * @return full field name.
     */
    public String getFullFieldName()
    {
        if (className != null)
        {
            return className + "." + name;
        }
        return getClassName(true) + "." + name;
    }

    /**
     * Accessor for whether the field is for a superclass, and not for this class.
     * @return Whether the field belongs to a superclass
     */
    public boolean fieldBelongsToClass()
    {
        return (className == null);
    }

    /**
     * Accessor for the fully-qualified class name owning this field.
     * @return The class name
     */
    public String getClassName()
    {
        return getClassName(true);
    }

    /**
     * Convenience method so that ClassMetaData can update the name of the superclass
     * to which this field belongs.
     * @param className Name of the class
     */
    void setClassName(String className)
    {
        this.className = className;
    }

    /**
     * Convenience to return the class name that this a field of.
     * @param fully_qualified Whether the name should be fully qualified.
     * @return Class name
     */
    public String getClassName(boolean fully_qualified)
    {
        if (className != null)
        {
            return className;
        }

        if (parent == null)
        {
            return null;
        }
        else if (parent instanceof AbstractClassMetaData)
        {
            AbstractClassMetaData cmd = (AbstractClassMetaData)parent;
            if (fully_qualified)
            {
                return cmd.getFullClassName();
            }
            else
            {
                return cmd.getName();
            }
        }
        else if (parent instanceof EmbeddedMetaData)
        {
            // <embedded> is contained in a <field>, <element>, <key>, <value>
            MetaData parentMd = ((EmbeddedMetaData)parent).getParent();
            if (parentMd instanceof AbstractMemberMetaData)
            {
                return ((AbstractMemberMetaData)parentMd).getTypeName();
            }
            else if (parentMd instanceof ElementMetaData)
            {
                AbstractMemberMetaData fmd = (AbstractMemberMetaData)((ElementMetaData)parentMd).getParent();
                return fmd.getCollection().getElementType();
            }
            else if (parentMd instanceof KeyMetaData)
            {
                AbstractMemberMetaData fmd = (AbstractMemberMetaData)((KeyMetaData)parentMd).getParent();
                return fmd.getMap().getKeyType();
            }
            else if (parentMd instanceof ValueMetaData)
            {
                AbstractMemberMetaData fmd = (AbstractMemberMetaData)((ValueMetaData)parentMd).getParent();
                return fmd.getMap().getValueType();
            }
            else
            {
                // Should be impossible
                return null;
            }
        }
        else if (parent instanceof UniqueMetaData)
        {
            MetaData grandparent = ((UniqueMetaData)parent).getParent();
            if (grandparent instanceof AbstractClassMetaData)
            {
                return ((AbstractClassMetaData)grandparent).getFullClassName();
            }
            // TODO Cater for other parent options
        }
        return null;
    }

    /**
     * Accessor for the null-value tag
     * @return null-value tag
     */
    public NullValue getNullValue()
    {
        return nullValue;
    }

    /**
     * Accessor for the persistence-modifier tag value
     * @return persistence-modifier tag value
     */
    public FieldPersistenceModifier getPersistenceModifier()
    {
        return persistenceModifier;
    }

    /**
     * Convenience method to mark this field as not-persistent.
     */
    public void setNotPersistent()
    {
        persistenceModifier = FieldPersistenceModifier.NONE;
    }

    /**
     * Convenience method to mark this field as transactional.
     */
    public void setTransactional()
    {
        persistenceModifier = FieldPersistenceModifier.TRANSACTIONAL;
    }

    /**
     * Accessor for the default-fetch-group tag value
     * @return default-fetch-group tag value
     */
    public boolean isDefaultFetchGroup()
    {
        if (defaultFetchGroup == null)
        {
            return false;
        }
        else
        {
            return defaultFetchGroup.booleanValue();
        }
    }

    /**
     * Convenience method to set the DFG if needing setting after construction.
     * @param dfg DFG string value
     */
    public void setDefaultFetchGroup(boolean dfg)
    {
         this.defaultFetchGroup = new Boolean(dfg);
    }

    /**
     * Accessor for the dependent attribute indicates that the field contains a
     * reference that is to be deleted from the datastore if the referring
     * instance in which the field is declared is deleted, or if the referring
     * field is nullified.
     *
     * @return dependent tag value
     */
    public boolean isDependent()
    {
        if (dependent == null)
        {
            return false;
        }
        else
        {
            return dependent.booleanValue();
        }
    }

    /**
     * Accessor for the embedded tag value.
     * This value is a hint only to the implementation so will not mean
     * that the type is certainly embedded.
     * @return embedded tag value
     */
    public boolean isEmbedded()
    {
        if (embedded == null)
        {
            return false;
        }
        else
        {
            return embedded.booleanValue();
        }
    }

    /**
     * Accessor for the serialized tag value
     * @return serialized tag value
     */
    public boolean isSerialized()
    {
        if (serialized == null)
        {
            return false;
        }
        else
        {
            return serialized.booleanValue();
        }
    }

    /**
     * Convenience method to mark this field to be stored serialised.
     */
    public void setSerialised()
    {
        serialized = Boolean.TRUE;
    }

    /**
     * Accessor for the whether this field should be cascaded at persist
     * @return Whether to cascade at persist
     */
    public boolean isCascadePersist()
    {
        return cascadePersist;
    }

    /**
     * Accessor for the whether this field should be cascaded at update
     * @return Whether to cascade at update
     */
    public boolean isCascadeUpdate()
    {
        return cascadeUpdate;
    }

    /**
     * Accessor for the whether this field should be cascaded at delete
     * @return Whether to cascade at delete
     */
    public boolean isCascadeDelete()
    {
        return cascadeDelete;
    }

    /**
     * Accessor for the whether this field should be cascaded at refresh
     * @return Whether to cascade at refresh
     */
    public boolean isCascadeRefresh()
    {
        return cascadeRefresh;
    }

    /**
     * Accessor for the primary-key tag value.
     * @return primary-key tag value.
     */
    public boolean isPrimaryKey()
    {
        if (primaryKey == null)
        {
            return false;
        }
        else
        {
            return primaryKey.booleanValue();
        }
    }

    /**
     * Convenience method to make this field (part of) the primary key.
     */
    public void setPrimaryKey()
    {
        primaryKey = Boolean.TRUE;
        this.defaultFetchGroup = Boolean.TRUE;
    }

    /**
     * Whether this uses getter/setter accessors (Property) or
     * used field based access (Field)
     * @return true if this is a property
     */
    public abstract boolean isProperty();

    /**
     * Accessor for the table name
     * @return table name
     */
    public String getTable()
    {
        return table;
    }

    /**
     * Accessor for the catalog name
     * @return catalog name
     */
    public String getCatalog()
    {
        return catalog;
    }

    /**
     * Accessor for the schema name
     * @return schema name
     */
    public String getSchema()
    {
        return schema;
    }

    /**
     * Accessor for the field id.
     * Not set when the field is an overriding field.
     * @return field id
     */
    public int getFieldId()
    {
        return fieldId;
    }

    /**
     * Accessor for the implementation type(s) that can be stored in this field when it is a reference type.
     * @return Returns the implementation type(s) for the field.
     */
    public final String[] getFieldTypes()
    {
        return fieldTypes;
    }

    /**
     * Accessor for the field id
     * @return field id
     */
    public int getAbsoluteFieldNumber()
    {
        if (className == null)
        {
            // Normal field, parented by its true class
            return fieldId + getAbstractClassMetaData().getNoOfInheritedManagedMembers();
        }
        else
        {
            // Overriding field, parented by a foster class
            return getAbstractClassMetaData().getAbsolutePositionOfMember(name);
        }
    }
   
    /**
     * Accessor for the field type
     * @return Reflection field type
     */
    public Class getType()
    {
        return type;
    }

    /**
     * Accessor for the field type name
     * @return Reflection field type name
     */
    public String getTypeName()
    {
        if (type == null)
        {
            return null;
        }
        return type.getName();
    }

    /**
     * Accessor for the container for this field.
     * @return The MetaData of the container for this field.
     **/
    public ContainerMetaData getContainer()
    {
        return container;
    }

    /**
     * Accessor for an array container for this field. Returns null if no array
     * attached.
     * @return The MetaData of the container for this field if an array
     **/
    public ArrayMetaData getArray()
    {
        if (container != null && container instanceof ArrayMetaData)
        {
            return (ArrayMetaData)container;
        }
        return null;
    }

    /**
     * Accessor for a collection container for this field. Returns null if no
     * collection attached.
     * @return The MetaData of the container for this field if a Collection.
     **/
    public CollectionMetaData getCollection()
    {
        if (container != null && container instanceof CollectionMetaData)
        {
            return (CollectionMetaData)container;
        }
        return null;
    }

    /**
     * Accessor for a map container for this field. Returns null if no map
     * attached.
     * @return The MetaData of the container for this field if a Map.
     **/
    public MapMetaData getMap()
    {
        if (container != null && container instanceof MapMetaData)
        {
            return (MapMetaData)container;
        }
        return null;
    }

    /**
     * Accessor for the column
     * @return Returns the column.
     */
    public final String getColumn()
    {
        return column;
    }

    /**
     * Accessor for mappedBy
     * @return Returns the mappedBy.
     */
    public final String getMappedBy()
    {
        return mappedBy;
    }

    /**
     * Acessor for the columns
     * @return Returns the columnMetaData.
     */
    public final ColumnMetaData[] getColumnMetaData()
    {
        return columnMetaData;
    }

    /**
     * Accessor for elementMetaData
     * @return Returns the elementMetaData.
     */
    public final ElementMetaData getElementMetaData()
    {
        return elementMetaData;
    }

    /**
     * Accessor for keyMetaData
     * @return Returns the keyMetaData.
     */
    public final KeyMetaData getKeyMetaData()
    {
        return keyMetaData;
    }

    /**
     * Accessor for valueMetaData
     * @return Returns the valueMetaData.
     */
    public final ValueMetaData getValueMetaData()
    {
        return valueMetaData;
    }

    /**
     * Accessor for embeddedMetaData
     * @return Returns the embeddedMetaData.
     */
    public final EmbeddedMetaData getEmbeddedMetaData()
    {
        return embeddedMetaData;
    }

    /**
     * Accessor for foreignKeyMetaData
     * @return Returns the foreignKeyMetaData.
     */
    public final ForeignKeyMetaData getForeignKeyMetaData()
    {
        return foreignKeyMetaData;
    }

    /**
     * Accessor for indexMetaData
     * @return Returns the indexMetaData.
     */
    public final IndexMetaData getIndexMetaData()
    {
        return indexMetaData;
    }

    /**
     * Accessor for uniqueMetaData
     * @return Returns the uniqueMetaData.
     */
    public final UniqueMetaData getUniqueMetaData()
    {
        return uniqueMetaData;
    }

    /**
     * Accessor for joinMetaData
     * @return Returns the joinMetaData.
     */
    public final JoinMetaData getJoinMetaData()
    {
        return joinMetaData;
    }

    /**
     * Add a new ColumnMetaData element
     * @param colmd the ColumnMetaData to add
     */
    public void addColumn(ColumnMetaData colmd)
    {
        columns.add(colmd);
        colmd.parent = this;
        columnMetaData = new ColumnMetaData[columns.size()];
        for (int i=0; i<columnMetaData.length; i++)
        {
            columnMetaData[i] = (ColumnMetaData) columns.get(i);
        }
    }

    /**
     * Accessor for whether the field has a container.
     * @return Whether it represents a container.
     */
    public boolean hasContainer()
    {
        return (container != null);
    }

    /**
     * Accessor for whether the field has an array
     * @return return true if has array
     */
    public boolean hasArray()
    {
        if (container == null)
        {
            return false;
        }
        return (container instanceof ArrayMetaData);
    }

    /**
     * Accessor for whether the field has a collection
     * @return return true if has collection
     */
    public boolean hasCollection()
    {
        if (container == null)
        {
            return false;
        }
        return (container instanceof CollectionMetaData);
    }

    /**
     * Accessor for whether the field has a map.
     * @return return true if has map
     */
    public boolean hasMap()
    {
        if (container == null)
        {
            return false;
        }
        return (container instanceof MapMetaData);
    }

    /**
     * Accessor for the JDO field flag
     * @return JDO Field flag (for enhancing)
     */
    public byte getJdoFieldFlag()
    {
        return jdoFieldFlag;
    }

    /**
     * Accessor for whether the field is to be managed by JPOX.
     * @return Whether it is JPOX managed
     */
    public boolean isJdoField()
    {
        if (isPopulated())
        {
            if (isStatic() || isFinal())
            {
                return false;
            }
        }

        if (persistenceModifier == null)
        {
            return false;
        }
        else if (persistenceModifier.equals(FieldPersistenceModifier.NONE))
        {
            return false;
        }
        return true;
    }

    // ------------------------------ Mutators ---------------------------------

    /**
     * Mutator for whether the collection stored in this field is ordered.
     * Only valid until the metadata is initialised.
     */
    public void setOrdered()
    {
        ordered = true;
    }

    /**
     * Mutator for the target class name. Only valid until the metadata is initialised.
     * @param target Target class name
     */
    public void setTargetClassName(String target)
    {
        if (!StringUtils.isWhitespace(target))
        {
            this.targetClassName = target;
        }
    }

    /**
     * Mutator for whetehr to store as a "lob".
     */
    public void setStoreInLob()
    {
        storeInLob = true;
    }

    /**
     * Mutator for the cascading of persist operations on this field.
     * @param cascade Whether to cascade at persist
     */
    public void setCascadePersist(boolean cascade)
    {
        this.cascadePersist = cascade;
    }

    /**
     * Mutator for the cascading of update operations on this field.
     * @param cascade Whether to cascade at update
     */
    public void setCascadeUpdate(boolean cascade)
    {
        this.cascadeUpdate = cascade;
    }

    /**
     * Mutator for the cascading of delete operations on this field.
     * @param cascade Whether to cascade at delete
     */
    public void setCascadeDelete(boolean cascade)
    {
        this.cascadeDelete = cascade;
    }

    /**
     * Mutator for the cascading of refresh operations on this field.
     * @param cascade Whether to cascade at refresh
     */
    public void setCascadeRefresh(boolean cascade)
    {
        this.cascadeRefresh = cascade;
    }

    /**
     * The value-strategy attribute specifies the strategy used to generate
     * values for the field. This attribute has the same values and meaning as
     * the strategy attribute in datastoreidentity.
     * @param valueStrategy the value strategy
     */
    public void setValueStrategy(IdentityStrategy valueStrategy)
    {
        this.valueStrategy = valueStrategy;
    }

    /**
     * Mutator for the name of the value generator to use for this strategy.
     * @param generator Name of value generator
     */
    public void setValueGeneratorName(String generator)
    {
        if (StringUtils.isWhitespace(generator))
        {
            valueGeneratorName = null;
        }
        else
        {
            this.valueGeneratorName = generator;
        }
    }

    /**
     * If the value-strategy is sequence, the sequence attribute specifies the
     * name of the sequence to use to automatically generate a value for the field.
     * @param sequence the sequence name
     */
    public void setSequence(String sequence)
    {
        this.sequence = sequence;
    }

    /**
     * Mutator for dependent attribute.
     * @param dependent Whether it is dependent.
     */
    public void setDependent(boolean dependent)
    {
        this.dependent = new Boolean(dependent);
    }

    /**
     * Mutator for mappedBy
     * @param mappedby The mapped-by field.
     */
    public void setMappedBy(String mappedby)
    {
        mappedBy = mappedby;
    }

    /**
     * Method to set the container for this field (if this field represents a
     * container (collection, map, array).
     * @param conmd The MetaData of the container for this field.
     **/
    public void setContainer(ContainerMetaData conmd)
    {
        container = conmd;
        container.parent = this;
    }

    /**
     * Mutator for the element MetaData
     * @param elementMetaData The elementMetaData to set.
     */
    public final void setElementMetaData(ElementMetaData elementMetaData)
    {
        this.elementMetaData = elementMetaData;
        this.elementMetaData.parent = this;
    }

    /**
     * Mutator for the key MetaData
     * @param keyMetaData The keyMetaData to set.
     */
    public final void setKeyMetaData(KeyMetaData keyMetaData)
    {
        this.keyMetaData = keyMetaData;
        this.keyMetaData.parent = this;
    }

    /**
     * Mutator for the order MetaData
     * @param orderMetaData The orderMetaData to set.
     */
    public final void setOrderMetaData(OrderMetaData orderMetaData)
    {
        this.orderMetaData = orderMetaData;
        this.orderMetaData.parent = this;
    }

    /**
     * Mutator for the value MetaData
     * @param valueMetaData The valueMetaData to set.
     */
    public final void setValueMetaData(ValueMetaData valueMetaData)
    {
        this.valueMetaData = valueMetaData;
        this.valueMetaData.parent = this;
    }

    /**
     * Mutator for the embedded MetaData
     * @param embeddedMetaData The embeddedMetaData to set.
     */
    public final void setEmbeddedMetaData(EmbeddedMetaData embeddedMetaData)
    {
        this.embeddedMetaData = embeddedMetaData;
        this.embeddedMetaData.parent = this;
    }

    /**
     * Mutator for the foreignKey MetaData
     * @param foreignKeyMetaData The foreignKeyMetaData to set.
     */
    public final void setForeignKeyMetaData(ForeignKeyMetaData foreignKeyMetaData)
    {
        this.foreignKeyMetaData = foreignKeyMetaData;
    }

    /**
     * Mutator for the index MetaData
     * @param indexMetaData The indexMetaData to set.
     */
    public final void setIndexMetaData(IndexMetaData indexMetaData)
    {
        this.indexMetaData = indexMetaData;
    }

    /**
     * Mutator for the unique MetaData
     * @param uniqueMetaData The uniqueMetaData to set.
     */
    public final void setUniqueMetaData(UniqueMetaData uniqueMetaData)
    {
        this.uniqueMetaData = uniqueMetaData;
    }

    /**
     * Mutator for the join MetaData
     * @param joinMetaData The joinMetaData to set.
     */
    public final void setJoinMetaData(JoinMetaData joinMetaData)
    {
        this.joinMetaData = joinMetaData;
        this.joinMetaData.parent = this;
    }

    /**
     * Mutator for the field id.
     * Given package access since updated by ClassMetaData typically.
     * Only used when the field is not an overriding field.
     * @param field_id Id of the field
     */
    void setFieldId(int field_id)
    {
        fieldId = field_id;
    }

    /**
     * Mutator for the table name
     * @param table The table name
     */
    public void setTable(String table)
    {
        this.table = (StringUtils.isWhitespace(table) ? null : table);
    }

    /**
     * Mutator for the catalog name
     * @param catalog The catalog name
     */
    public void setCatalog(String catalog)
    {
        this.catalog = (StringUtils.isWhitespace(catalog) ? null : catalog);
    }

    /**
     * Mutator for the schema name
     * @param schema The schema name
     */
    public void setSchema(String schema)
    {
        this.schema = (StringUtils.isWhitespace(schema) ? null : schema);
    }

    // ------------------------------ Utilities --------------------------------

    /**
     * Convenience method that sets up the relation type of this field, and the reference to
     * any related field when it is bidirectional. If the relation is bidirectional then will also
     * set the other side of the relation (to relate to this side).
     * @param clr ClassLoader resolver
     * @throws JPOXUserException If mapped-by doesnt exist at other side
     */
    protected void setRelation(ClassLoaderResolver clr)
    {
        if (getPersistenceModifier() != FieldPersistenceModifier.PERSISTENT)
        {
            // Only of use for persistent fields
            relationType = Relation.NONE;
            relatedMemberMetaData = null;
            return;
        }

        // Find the metadata for the field object
        AbstractClassMetaData otherCmd = null;
        if (hasCollection())
        {
            otherCmd = getCollection().getElementClassMetaData();
            if (otherCmd == null)
            {
                // Maybe a reference field
                Class elementCls = clr.classForName(getCollection().getElementType());
                if (getMetaDataManager().getOMFContext().getTypeManager().isReferenceType(elementCls))
                {
                    try
                    {
                        String[] implNames = MetaDataUtils.getInstance().getImplementationNamesForReferenceField(this,
                            FieldRole.ROLE_COLLECTION_ELEMENT, clr);
                        if (implNames != null && implNames.length > 0)
                        {
                            // Take the first implementation
                            otherCmd = getMetaDataManager().getMetaDataForClass(implNames[0], clr);
                        }
                    }
                    catch (JPOXUserException jpe)
                    {
                        if (!getCollection().isSerializedElement() && mappedBy != null)
                        {
                            // Non-serialised, and with mapped-by so we need implementation classes
                            throw jpe;
                        }
                        else
                        {
                            // Serialised element with no implementation types so ignore it
                            JPOXLogger.METADATA.info("Field " + getFullFieldName() +
                                " is a collection of elements of reference type yet no implementation-classes are provided." +
                                " Assuming they arent PersistenceCapable");
                        }
                    }
                }
            }
        }
        else if (hasMap())
        {
            otherCmd = ((MapMetaData) container).getValueClassMetaData();
            //TODO [CORE-2585] valueCMD may be null because its type is an interface (non persistent interface),
            //so we should handle the implementation classes
            if (otherCmd == null)
            {
                // Value not PC so use the Key if it is specified
                otherCmd = ((MapMetaData)container).getKeyClassMetaData();
            }
            if (otherCmd == null)
            {
                // Maybe a reference key/value
            }
        }
        else if (hasArray())
        {
            otherCmd = ((ArrayMetaData)container).getElementClassMetaData();
        }
        else
        {
            if (getType().isInterface())
            {
                // Reference field - take the metadata of the first implementation
                String[] implNames = MetaDataUtils.getInstance().getImplementationNamesForReferenceField(this, FieldRole.ROLE_FIELD, clr);
                if (implNames != null && implNames.length > 0)
                {
                    otherCmd = getMetaDataManager().getMetaDataForClass(implNames[0], clr);
                }
                /*otherCmd = getMetaDataManager().getMetaDataForImplementationOfReference(getType(), null, clr);*/
                /*otherCmd = getAbstractClassMetaData().getMetaDataManager().getMetaDataForInterface(getType(), clr);*/
            }
            else
            {
                otherCmd = getMetaDataManager().getMetaDataForClass(getType(), clr);
            }
        }

        //TODO [CORE-2585] when the element or value type is an interface (non persistent interface),
        //we should look at the implementation classes for the "otherCmd"
        //for now the relationType will be Relation.NONE in these cases because the otherCmd is null
        if (otherCmd == null)
        {
            if (hasArray() && getArray().mayContainPersistenceCapableElements())
            {
                relatedMemberMetaData = null;
                relationType = Relation.ONE_TO_MANY_UNI;
            }
            else
            {
                // Field cannot have a relation
                relatedMemberMetaData = null;
                relationType = Relation.NONE;
            }
        }
        else
        {
            // Field is bidirectional
            if (mappedBy != null)
            {
                // This class has the "mapped-by" specified
                AbstractMemberMetaData otherMmd = otherCmd.getMetaDataForMember(mappedBy);
                if (otherMmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("044115",
                        getAbstractClassMetaData().getFullClassName(), name, mappedBy, otherCmd.getFullClassName())).setFatal();
                }

                relatedMemberMetaData = new AbstractMemberMetaData[] {otherMmd};
                if (hasContainer() && relatedMemberMetaData[0].hasContainer())
                {
                    relationType = Relation.MANY_TO_MANY_BI;
                }
                else if (hasContainer() && !relatedMemberMetaData[0].hasContainer())
                {
                    relationType = Relation.ONE_TO_MANY_BI;
                }
                else if (!hasContainer() && relatedMemberMetaData[0].hasContainer())
                {
                    relationType = Relation.MANY_TO_ONE_BI;
                }
                else
                {
                    relationType = Relation.ONE_TO_ONE_BI;
                }
            }
            else
            {
                // The "other" class maybe has "mapped-by" across to this field so navigate through the fields to find this one
                int[] otherFieldNumbers = otherCmd.getAllMemberPositions();
                HashSet relatedFields = new HashSet();
                for (int i=0;i<otherFieldNumbers.length;i++)
                {
                    AbstractMemberMetaData otherFmd = otherCmd.getMetaDataForManagedMemberAtAbsolutePosition(otherFieldNumbers[i]);
                    if (otherFmd.getMappedBy() != null && otherFmd.getMappedBy().equals(name))
                    {
                        // e.g Look at org.jpox.samples.inheritance.marbles
                        if (otherFmd.hasContainer())
                        {
                            // N-1, M-N
                            if ((otherFmd.hasCollection() && otherFmd.getCollection().getElementType().equals(getClassName(true))) ||
                                (otherFmd.hasArray() && otherFmd.getArray().getElementType().equals(getClassName(true))) ||
                                (otherFmd.hasMap() && otherFmd.getMap().getKeyType().equals(getClassName(true))) ||
                                (otherFmd.hasMap() && otherFmd.getMap().getValueType().equals(getClassName(true))))
                            {
                                relatedFields.add(otherFmd);
                                if (hasContainer())
                                {
                                    // Should we mark Arrays, Lists, Maps as M-N ?
                                    relationType = Relation.MANY_TO_MANY_BI;
                                }
                                else
                                {
                                    relationType = Relation.MANY_TO_ONE_BI;
                                }
                            }
                        }
                        else
                        {
                            // 1-1, 1-N
                            Class cls = clr.classForName(getClassName(true));
                            if (otherFmd.getType().isAssignableFrom(cls) || cls.isAssignableFrom(otherFmd.getType()))
                            {
                                // Consistent 1-1, 1-N types (allow subclasses of the defined types)
                                relatedFields.add(otherFmd);
                                if (hasContainer())
                                {
                                    relationType = Relation.ONE_TO_MANY_BI;
                                }
                                else
                                {
                                    relationType = Relation.ONE_TO_ONE_BI;
                                }
                            }
                        }
                    }
                }
                if (relatedFields.size() > 0)
                {
                    relatedMemberMetaData = (AbstractMemberMetaData[])relatedFields.toArray(new AbstractMemberMetaData[relatedFields.size()]);
                    relatedFields.clear();
                    relatedFields = null;
                }
                else
                {
                    // No "mapped-by" found at either end so is unidirectional
                    if (hasContainer())
                    {
                        relationType = Relation.ONE_TO_MANY_UNI;
                    }
                    else
                    {
                        relationType = Relation.ONE_TO_ONE_UNI;
                    }
                }
            }
        }
    }

    /**
     * Accessor for the relation type for this field.
     * @param clr ClassLoader resolver
     * @return The relation type.
     */
    public int getRelationType(ClassLoaderResolver clr)
    {
        if (relationType == -1)
        {
            // Is possible that this could be done in the populate() step but depends on availability of the related class
            setRelation(clr);
        }
        return relationType;
    }

    /**
     * Convenience method for whether this field is the owner of the relation.
     * If the field has no relation will return true.
     * If the field is in a unidirectional relation will return true.
     * If the field is in a bidirectional relation and has no mapped-by will return true.
     * Otherwise returns false.
     * @param clr ClassLoader resolver
     * @return Whether it is the owner side of a relation
     */
    public boolean isRelationOwner(ClassLoaderResolver clr)
    {
        if (relationType == -1)
        {
            setRelation(clr);
        }
        if (relationType == Relation.NONE)
        {
            return true;
        }
        else if (relationType == Relation.ONE_TO_MANY_UNI || relationType == Relation.ONE_TO_ONE_UNI)
        {
            return true;
        }
        else if (relationType == Relation.MANY_TO_MANY_BI || relationType == Relation.MANY_TO_ONE_BI ||
            relationType == Relation.ONE_TO_MANY_BI || relationType == Relation.ONE_TO_ONE_BI)
        {
            return (mappedBy == null);
        }
        return false;
    }

    /**
     * Accessor for the FieldMetaData of any related field/property (where this field is part of a
     * bidirectional relation). Allows for 1-1, 1-N, and M-N. If this field is not part of a bidirectional
     * relation (no "mapped-by" at either end) then it returns null.
     * @param clr the ClassLoaderResolver
     * @return The MetaData for the field/property at the "other end".
     */
    public AbstractMemberMetaData[] getRelatedMemberMetaData(ClassLoaderResolver clr)
    {
        if (relationType == -1)
        {
            // Is possible that this could be done in the populate() step but depends on availability of the related class
            setRelation(clr);
        }
        // TODO This doesnt allow for 1-1, 1-N reference fields where there may be multiple "other fields".
        return relatedMemberMetaData;
    }

    /**
     * Convenience accessor for the MetaData for the field/property at the other side of the bidirectional
     * relation given the objects at this side and the other side.
     * @param clr ClassLoader Resolver
     * @param thisPC This object
     * @param otherPC The related object
     * @return The MetaData for the field in the related object
     */
    public AbstractMemberMetaData getRelatedMemberMetaDataForObject(ClassLoaderResolver clr, Object thisPC, Object otherPC)
    {
        if (relationType == -1)
        {
            setRelation(clr);
        }

        if (relatedMemberMetaData == null)
        {
            return null;
        }

        // TODO Cater for 1-N, M-N types
        for (int i=0;i<relatedMemberMetaData.length;i++)
        {
            if (relationType == Relation.ONE_TO_ONE_BI)
            {
                if (relatedMemberMetaData[i].getType().isAssignableFrom(thisPC.getClass()) &&
                    getType().isAssignableFrom(otherPC.getClass()))
                {
                    return relatedMemberMetaData[i];
                }
            }
            else if (relationType == Relation.MANY_TO_ONE_BI)
            {
                // Just allow for Collections
                if (relatedMemberMetaData[i].hasCollection())
                {
                    Class elementType = clr.classForName(relatedMemberMetaData[i].getCollection().getElementType());
                    if (elementType.isAssignableFrom(thisPC.getClass()) &&
                        getType().isAssignableFrom(otherPC.getClass()))
                    {
                        return relatedMemberMetaData[i];
                    }
                }
                else if (relatedMemberMetaData[i].hasMap())
                {
                    Class valueType = clr.classForName(relatedMemberMetaData[i].getMap().getValueType());
                    if (valueType.isAssignableFrom(thisPC.getClass()) &&
                        getType().isAssignableFrom(otherPC.getClass()))
                    {
                        return relatedMemberMetaData[i];
                    }
                    Class keyType = clr.classForName(relatedMemberMetaData[i].getMap().getKeyType());
                    if (keyType.isAssignableFrom(thisPC.getClass()) &&
                        getType().isAssignableFrom(otherPC.getClass()))
                    {
                        return relatedMemberMetaData[i];
                    }
                }
            }
        }
        return null;
    }

    /**
     * Accessor for all ClassMetaData referenced by this Field.
     * <p>
     * Part of the "persistence-by-reachability" concept.
     * @param orderedCMDs List of ordered ClassMetaData objects (added to).
     * @param referencedCMDs Set of referenced ClassMetaData objects (added to)
     * @param dba_vendor_id Vendor id of the DBA
     * @param clr the ClassLoaderResolver
     **/
    void getReferencedClassMetaData(final List orderedCMDs,
                                    final Set referencedCMDs,
                                    final String dba_vendor_id,
                                    final ClassLoaderResolver clr)
    {
        AbstractClassMetaData type_cmd=getAbstractClassMetaData().getMetaDataManager().getMetaDataForClass(getType(), clr);
        if (type_cmd != null)
        {
            type_cmd.getReferencedClassMetaData(orderedCMDs,referencedCMDs,dba_vendor_id,clr);
        }

        if (container != null)
        {
            if (container instanceof CollectionMetaData)
            {
                ((CollectionMetaData)container).getReferencedClassMetaData(orderedCMDs,referencedCMDs,dba_vendor_id,clr);
            }
            else if (container instanceof MapMetaData)
            {
                ((MapMetaData)container).getReferencedClassMetaData(orderedCMDs,referencedCMDs,dba_vendor_id,clr);
            }
            else if (container instanceof ArrayMetaData)
            {
                ((ArrayMetaData)container).getReferencedClassMetaData(orderedCMDs,referencedCMDs,dba_vendor_id,clr);
            }
        }
    }
   
    /**
     *  Calculate wether this field should be a second class mutable field.
     *  This calculation is a bit expensive.
     *  Please note that this data will be cached in {@link AbstractClassMetaData#scoMutableMemberFlags}.
     * 
     *  @return wether this field should be a second class mutable field.
     */
    public boolean calcIsSecondClassMutable()
    {
        if (hasExtension("is-second-class"))
        {
            String isSecondClass = getValueForExtension("is-second-class");
           
            if (isSecondClass.equalsIgnoreCase("true"))
            {
                return true;
            }
            else if (isSecondClass.equalsIgnoreCase("false"))
            {
                return false;
            }
            else if (isSecondClass.equalsIgnoreCase("default"))
            { // fall through to default behaviour
               
            }
            else
            {
                // Be nice to JDO mapping authors and remind them that they may have a misspelling.
                throw new UnsupportedOperationException(this+": extensions \"is-second-class\" may have only values \"true\", \"false\", \"default\" or it may not be set at all. But it is set to \""+isSecondClass+"\".");
            }
        }
        else
        { // fall through to default behaviour
           
        }
       
        return getMetaDataManager().getOMFContext().getTypeManager().isSecondClassMutableType(getTypeName());
    }

    /**
     * Returns a string representation of the object using a prefix
     * This can be used as part of a facility to output a MetaData file.
     * @param prefix prefix string
     * @param indent indent string
     * @return a string representation of the object.
     */
    public String toString(String prefix, String indent)
    {
        return super.toString(prefix, indent);
    }

    /**
     * Comparator method. This allows the ClassMetaData to search for a
     * FieldMetaData with a particular name.
     * @param o The object to compare against
     * @return The comparison result
     */
    public int compareTo(Object o)
    {
        if (o instanceof AbstractMemberMetaData)
        {
            AbstractMemberMetaData c = (AbstractMemberMetaData)o;
            return this.name.compareTo(c.name);
        }
        else if (o instanceof String)
        {
            return this.name.compareTo((String)o);
        }
        else if (o == null)
        {
            throw new ClassCastException("object is null");
        }
        throw new ClassCastException(this.getClass().getName() + " != " + o.getClass().getName());
    }

    /**
     * Convenience accessor for the MetaData Manager in use.
     * @return MetaDataManager.
     */
    public MetaDataManager getMetaDataManager()
    {
        MetaData overallCmd = getOverallParentClassMetaData(this);
        return (overallCmd != null ? ((AbstractClassMetaData)overallCmd).getMetaDataManager() : null);
    }
}
TOP

Related Classes of org.jpox.metadata.AbstractMemberMetaData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.