/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the "License").  You may not use this file except 
 * in compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt or 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html. 
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * HEADER in each file and include the License file at 
 * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable, 
 * add the following below this CDDL HEADER, with the 
 * fields enclosed by brackets "[]" replaced with your 
 * own identifying information: Portions Copyright [yyyy] 
 * [name of copyright owner]
 */
// Copyright (c) 1998, 2006, Oracle. All rights reserved.  
package oracle.toplink.essentials.internal.ejb.cmp3.metadata;

import java.util.Map;
import java.util.List;
import java.util.Vector;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collection;

import oracle.toplink.essentials.mappings.DatabaseMapping;

import oracle.toplink.essentials.exceptions.ValidationException;

import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.descriptors.VersionLockingPolicy;
import oracle.toplink.essentials.descriptors.RelationalDescriptor;

import oracle.toplink.essentials.internal.helper.DatabaseField;
import oracle.toplink.essentials.internal.helper.DatabaseTable;
import oracle.toplink.essentials.internal.ejb.cmp3.base.CMP3Policy;

/**
 * Common metatata descriptor for the annotation and xml processors.
 * 
 * @author Guy Pelletier, Dave McCann
 * @since TopLink EJB 3.0 Reference Implementation
 */
public abstract class MetadataDescriptor {
	protected ClassDescriptor m_descriptor;
    
    protected Class m_javaClass;
	protected Class m_inheritanceRootClass;
	protected MetadataDescriptor m_inheritanceRootDmd;
    
    protected boolean m_isInheritanceRoot;
    protected boolean m_ignoreIDAnnotations;
	protected boolean m_ignoreTableAnnotations;
    protected boolean m_ignoreInheritanceAnnotations;
	protected boolean m_usesSingleTableInheritanceStrategy;
    
	protected String m_primaryTableName;
	protected String m_embeddedIdAttributeName;
    
    protected Map m_accessors;
    protected Map m_pkClassIDs;
    protected Map m_setMethodNames;
    protected Map m_attributeOverrides;
    protected Map m_associationOverrides;
    protected Map m_manyToManyAccessors;
    protected Map m_relationshipAccessors;    
    
    protected List m_idAttributeNames;
    protected List m_idOrderByAttributeNames;
    protected List m_mappedSuperclasses;
    protected List m_aggregateDmds;
    protected List m_orderByAttributeNames;
    
    /********************* Values set during XML processing *********************/
    // Used to determine if access type was specified for this entity/descriptor.
    protected Boolean m_isXmlPropertyAccess;
    // Used to determine if cascade-persist was specified for this entity/descriptor.
    protected Boolean m_isCascadePersistSet;
    // By default we don't exclude default listeners unless explicitly set.
    protected boolean m_xmlExcludeDefaultListeners;
    // By default we don't exclude superclass listeners unless explicitely set.
    protected boolean m_xmlExcludeSuperclassListeners;	
    // True is meta-data complete is specified. Ignore annotations except for defaults.
    protected boolean m_ignoreAnnotations;
    // Only to be used if the entity is defined in XML and not annotated.
    protected String m_xmlCatalog;
    // Only to be used if the entity is defined in XML and not annotated.
    protected String m_xmlSchema;
    // List of default listeners to apply to this entity/descriptor
    protected List m_defaultListeners;					
    // Indicates no id element was defined in XML, so the default (TOPLINKDEFAULTID) was used.
    protected boolean m_isDefaultPrimaryKeySet;
    // Indicates no table element was defined in XML, so the default was used.
    protected boolean m_isDefaultPrimaryTableSet;
    // List of fields which have the default table name set.
    protected List m_fieldsWithDefaultPrimaryKeySet;
    // List of fields with name in the format, entity/attribute name + "_" + TOPLINKDEFAULTID
    protected List m_fieldsWithComplexDefaultPrimaryKeySet;
    // List of fields with name = TOPLINKDEFAULTID
    protected List m_fieldsWithDefaultPrimaryTableSet;
    
    /**
     * INTERNAL: 
     */
    public MetadataDescriptor(Class javaClass) {
        init();
        m_descriptor = new RelationalDescriptor();
        m_descriptor.setExistenceChecking("Check database");
        // In case of alias==null, TopLink populates it with short class name 
        // in desc.getAlias(), which causes descriptor being added with this 
        // alias to the project (addDescriptor method) before we have a chance 
        // to set the desired alias. Setting "" stops default alias generation.
        // WIP - fix this code.
        m_descriptor.setAlias("");
        setJavaClass(javaClass);
    }
    
    /**
     * INTERNAL:
     * Constructor used for wrapping an existing descriptor which was presumably 
     * loaded from the project xml.
     */
    public MetadataDescriptor(ClassDescriptor descriptor, Class javaClass) {
        init();
        m_descriptor = descriptor;
        setJavaClass(javaClass);
    }
     
    /**
     * INTERNAL:
     * Copy constructor. Used for creation of annotations meta data out of xml 
     * meta data. All the attributes should be processed.
     */
    public MetadataDescriptor(MetadataDescriptor md) {
        m_descriptor = md.m_descriptor;
        
        m_javaClass = md.m_javaClass;
        m_inheritanceRootClass = null;
        m_inheritanceRootDmd = null;
        
        m_isInheritanceRoot = md.m_isInheritanceRoot;
        m_ignoreIDAnnotations = md.m_ignoreIDAnnotations;
        m_ignoreTableAnnotations = md.m_ignoreIDAnnotations;
        m_ignoreInheritanceAnnotations = md.m_ignoreInheritanceAnnotations;
        m_usesSingleTableInheritanceStrategy = md.m_usesSingleTableInheritanceStrategy;

        m_primaryTableName = md.m_primaryTableName;
        m_embeddedIdAttributeName = md.m_embeddedIdAttributeName;
        
        m_pkClassIDs = md.m_pkClassIDs;
        m_setMethodNames = md.m_setMethodNames;
        m_attributeOverrides = md.m_attributeOverrides;
        m_associationOverrides = md.m_associationOverrides;
        m_accessors = md.m_accessors;
        m_manyToManyAccessors = md.m_manyToManyAccessors;
        m_relationshipAccessors = md.m_relationshipAccessors;
        
        m_idAttributeNames = md.m_idAttributeNames;
        m_idOrderByAttributeNames = md.m_idOrderByAttributeNames;
        m_mappedSuperclasses = md.m_mappedSuperclasses;
        m_aggregateDmds = md.m_aggregateDmds;
        m_orderByAttributeNames = md.m_orderByAttributeNames;
        
        m_isCascadePersistSet = md.m_isCascadePersistSet;
        m_xmlExcludeDefaultListeners = md.m_xmlExcludeDefaultListeners;
        m_xmlExcludeSuperclassListeners = md.m_xmlExcludeSuperclassListeners;
        m_ignoreAnnotations = md.m_ignoreAnnotations;
        m_defaultListeners = md.m_defaultListeners;
        m_xmlCatalog = md.m_xmlCatalog;
        m_xmlSchema = md.m_xmlSchema;
        
        m_isDefaultPrimaryKeySet = md.m_isDefaultPrimaryKeySet;
        m_isDefaultPrimaryTableSet = md.m_isDefaultPrimaryTableSet;
        m_fieldsWithDefaultPrimaryKeySet = md.m_fieldsWithDefaultPrimaryKeySet;
        m_fieldsWithComplexDefaultPrimaryKeySet = md.m_fieldsWithComplexDefaultPrimaryKeySet;
        m_fieldsWithDefaultPrimaryTableSet = md.m_fieldsWithDefaultPrimaryTableSet;
    }
     
     /**
      * INTERNAL:
      */
     public void addAccessor(MetadataAccessor accessor) {
         m_accessors.put(accessor.getAttributeName(), accessor);
         
         if (accessor.isRelationship()) {
             m_relationshipAccessors.put(accessor.getAttributeName(), accessor);
         }
         
         // Store ManyToMany relationships so that we may look at attribute
         // names when defaulting join columns for bi and uni directional M-M's.
         if (accessor.isManyToMany()) {
             m_manyToManyAccessors.put(accessor.getReferenceClass(), accessor);   
         }
     }
     
    /**
     * INTERNAL:
     */
    public void addAggregateDmd(MetadataDescriptor aggregateDmd) {
        m_aggregateDmds.add(aggregateDmd);
    }
    
    /**
     * INTERNAL:
     */
     public void addAssociationOverride(String attributeName, Object[] joinColumns) {
        m_associationOverrides.put(attributeName, joinColumns);   
     }
    
    /**
     * INTERNAL:
     */
    public void addAttributeOverride(String attributeName, DatabaseField field) {
        m_attributeOverrides.put(attributeName, field);
    }
    
    /**
     * INTERNAL:
     */
    public void addClassIndicator(Class entityClass, String value) {
        if (isInheritanceSubclass()) {
            getInheritanceRootDmd().addClassIndicator(entityClass, value);   
        } else {
            m_descriptor.getInheritancePolicy().addClassNameIndicator(entityClass.getName(), value);
        }
    }
    
    /** 
     * INTERNAL:
     */
    public void addDefaultEventListener(MetadataEntityListener listener) {
        m_descriptor.getEventManager().addDefaultEventListener(listener);
    }
    
    /** 
     * INTERNAL:
     */
    public void addEntityEventListener(MetadataEntityListener listener) {
        m_descriptor.getEventManager().setEntityEventListener(listener);
    }
    
    /**
     * INTERNAL:
     */
    public void addEntityListenerEventListener(MetadataEntityListener listener) {
        m_descriptor.getEventManager().addEntityListenerEventListener(listener);
    }
    
    /**
     * INTERNAL:
     */
    public void addIdAttributeName(String idAttributeName) {
        m_idAttributeNames.add(idAttributeName);    
    }
    
    /**
     * INTERNAL:
     */
    public void addListener(MetadataEntityListener listener) {
        m_descriptor.getEventManager().addListener(listener);
    }
    
    /**
     * INTERNAL:
     */
    public void addMapping(DatabaseMapping mapping) {
        m_descriptor.addMapping(mapping);
    }
    
    /**
     * INTERNAL:
     */
    public void addMultipleTableForeignKeyField(DatabaseField pkField, DatabaseField fkField) {
        m_descriptor.addMultipleTableForeignKeyField(pkField, fkField);
    }
    
    /**
     * INTERNAL:
     */
    public void addMultipleTablePrimaryKeyField(DatabaseField pkField, DatabaseField fkField) {
        m_descriptor.addMultipleTablePrimaryKeyField(pkField, fkField);
    }
    
    /**
     * INTERNAL:
     * We store these to validate the primary class when processing
     * the entity class.
     */
    public void addPKClassId(String attributeName, Class type) {
        m_pkClassIDs.put(attributeName, type);
    }
    
    /**
     * INTERNAL:
     */
    public void addPrimaryKeyField(DatabaseField field) {
        m_descriptor.addPrimaryKeyField(field);
    }
    
    /**
     * INTERNAL:
     */
    public void addSetMethodName(String getMethodName, String setMethodName) {
        m_setMethodNames.put(getMethodName, setMethodName);
    }
    
    /**
     * INTERNAL:
     */
    public void addTable(DatabaseTable table) {
        m_descriptor.addTable(table);
    }
    
    /**
     * INTERNAL:
     */
    public boolean excludeSuperclassListeners() {
        return m_descriptor.getEventManager().excludeSuperclassListeners();
    }
    
    /**
     * INTERNAL:
     */
    public MetadataAccessor getAccessorFor(String fieldOrPropertyName) {
    	MetadataAccessor accessor = (MetadataAccessor) m_accessors.get(fieldOrPropertyName);
        
        if (accessor == null) {
            return (MetadataAccessor) m_accessors.get(MetadataHelper.getAttributeNameFromMethodName(fieldOrPropertyName));
        }
        
        return accessor;
    }
    
    /**
     * INTERNAL:
     */
    public String getAlias() {
        return m_descriptor.getAlias();
    }
    
    /**
     * INTERNAL:
     */
    public Object[] getAssociationOverrideFor(MetadataAccessor accessor) {
        return (Object[]) m_associationOverrides.get(accessor.getAttributeName());
    }
    
    /**
     * INTERNAL:
     */
    public DatabaseField getAttributeOverrideFor(MetadataAccessor accessor) {
        return getAttributeOverrideFor(accessor.getAttributeName());
    }
    
    /**
     * INTERNAL:
     */
    public DatabaseField getAttributeOverrideFor(String attributeName) {
        return (DatabaseField) m_attributeOverrides.get(attributeName);
    }

    /**
     * INTERNAL:
     */
    public DatabaseField getClassIndicatorField() {
        if (getInheritanceRootDmd() != null) {
            return getInheritanceRootDmd().getDescriptor().getInheritancePolicy().getClassIndicatorField();
        } else {
            if (getDescriptor().hasInheritance()) {
                return getDescriptor().getInheritancePolicy().getClassIndicatorField();
            } else {
                return null;
            }
        }
    }
    
    /**
     * INTERNAL:
     * A list of default listeners to be applied to this entity if
     * shouldExcludeDefaultListeners() is false.  The listeners are of type 
     * MetadataDefaultListener. The initializeCallbackMethods(ClassLoader) 
     * method must be called on each listener before adding them to the event 
     * manager.
     */
    public List getDefaultListeners() {
    	if (m_defaultListeners == null) {
    		m_defaultListeners = new ArrayList();
    	}
        
    	return m_defaultListeners;
    }

    /**
     * INTERNAL:
     * The default table name is the descriptor alias, unless this descriptor 
     * metadata is an inheritance subclass with a SINGLE_TABLE strategy. Then 
     * it is the table name of the root descriptor metadata.
     */
    public String getDefaultTableName() {
        String defaultTableName = getAlias().toUpperCase();
        
        if (isInheritanceSubclass()) {    
            if (m_inheritanceRootDmd.usesSingleTableInheritanceStrategy()) {
                defaultTableName = m_inheritanceRootDmd.getPrimaryTableName();
            }
        }
        
        return defaultTableName;
    }
    
    /**
     * INTERNAL:
     */
    public ClassDescriptor getDescriptor() {
        return m_descriptor;
    }
    
    /**
     * INTERNAL:
     * The value for m_catalog will be one of:
     * 	- persistence unit default
     * 	- entity-mappings default
     * 	- empty string
     */
    public String getCatalog() {
    	return m_xmlCatalog;
    }
    
    /**
     * INTERNAL:
     */
    public String getEmbeddedIdAttributeName() {
        return m_embeddedIdAttributeName;
    }
    
    /**
     * INTERNAL:
     */
    public List getFieldsWithComplexDefaultPrimaryKeySet() {
    	if (m_fieldsWithComplexDefaultPrimaryKeySet == null) {
    		m_fieldsWithComplexDefaultPrimaryKeySet = new ArrayList();
    	}
    	return m_fieldsWithComplexDefaultPrimaryKeySet;
    }

    /**
     * INTERNAL:
     */
    public List getFieldsWithDefaultPrimaryKeySet() {
    	if (m_fieldsWithDefaultPrimaryKeySet == null) {
    		m_fieldsWithDefaultPrimaryKeySet = new ArrayList();
    	}
    	return m_fieldsWithDefaultPrimaryKeySet;
    }
    
    /**
     * INTERNAL:
     */
    public List getFieldsWithDefaultPrimaryTableSet() {
    	if (m_fieldsWithDefaultPrimaryTableSet == null) {
    		m_fieldsWithDefaultPrimaryTableSet = new ArrayList();
    	}
    	return m_fieldsWithDefaultPrimaryTableSet;
    }
    
    /**
     * INTERNAL:
     * Return the primary key attribute name for this entity.
     */
    public String getIdAttributeName() {
        if (getIdAttributeNames().isEmpty()) {
            if (isInheritanceSubclass()) {
                return getInheritanceRootDmd().getIdAttributeName();
            } else {
                return "";
            }
        } else {
            return (String) getIdAttributeNames().get(0);
        }
    }
    
    /**
     * INTERNAL:
     * Return the id attribute names declared on this descriptor metadata.
     */
    public List getIdAttributeNames() {
        return m_idAttributeNames;
    }
    
    /**
     * INTERNAL:
     * Return the primary key attribute names for this entity. If there are no
     * id attribute names set then we are either:
     * 1) an inheritance subclass, get the id attribute names from the root
     *    of the inheritance structure.
     * 2) we have an embedded id. Get the id attribute names from the embedded
     *    descriptor metadata, which is equal the attribute names of all the
     *    direct to field mappings on that descriptor metadata. Currently does
     *    not traverse nested embeddables.
     */
    public List getIdOrderByAttributeNames() {
        if (m_idOrderByAttributeNames.isEmpty()) {
            if (m_idAttributeNames.isEmpty()) {
                if (isInheritanceSubclass()) {  
                    // Get the id attribute names from our root parent.
                    m_idOrderByAttributeNames = m_inheritanceRootDmd.getIdAttributeNames();
                } else {
                    // We must have a composite primary key as a result of an embedded id.
                    m_idOrderByAttributeNames = getAccessorFor(getEmbeddedIdAttributeName()).getReferenceMetadataDescriptor().getOrderByAttributeNames();
                } 
            } else {
                m_idOrderByAttributeNames = m_idAttributeNames;
            }
        }
            
        return m_idOrderByAttributeNames;
    }
    
    
    /**
     * INTERNAL:
     */
    public MetadataAccessor getManyToManyAccessor(Class cls) {
        return (MetadataAccessor) m_manyToManyAccessors.get(cls);
    }
    
    /**
     * INTERNAL:
     * This will return the attribute names for all the direct to field mappings 
     * on this descriptor metadata. This method will typically be called when an 
     * @Embedded or @EmbeddedId attribute has been specified in an @OrderBy.
     */
    public List getOrderByAttributeNames() {
        if (m_orderByAttributeNames.isEmpty()) {
        	for (Iterator mapIt = getMappings().iterator(); mapIt.hasNext(); ) {
        		DatabaseMapping mapping = (DatabaseMapping) mapIt.next();
                if (mapping.isDirectToFieldMapping()) {
                    m_orderByAttributeNames.add(mapping.getAttributeName());
                }
            }
        }
        
        return m_orderByAttributeNames;
    }

    /**
     * INTERNAL:
     * This method assumes that isInheritanceSubclass(HashMap) has already been 
     * called to determine the inheritance root class. It returns the root 
     * parent class in an inheritance hierarchy, null otherwise.
     */
	public Class getInheritanceRootClass() {
        return m_inheritanceRootClass;
    }
	
    /**
     * INTERNAL: 
     * Store the descriptor metadata for the root of our inheritance hierarchy.
     */
    public MetadataDescriptor getInheritanceRootDmd() {
        return m_inheritanceRootDmd;
    }

    /**
     * INTERNAL:
     */
    public Class getJavaClass() {
        return m_javaClass;
    }
    
    /**
     * INTERNAL:
     */
    public DatabaseMapping getMappingForAttributeName(String attributeName) {
        MetadataAccessor accessor = (MetadataAccessor) getAccessorFor(attributeName);
        
        if (accessor != null) {
            // If the accessor is a relationship accessor than it may or may
            // not have been processed yet. Fast track its processing if it
            // needs to be. The process call will do nothing if it has already
            // been processed.
            if (accessor.isRelationship()) {
                accessor.process();
            }
            
            return m_descriptor.getMappingForAttributeName(attributeName);
        }
        
        // We didn't find a mapping on this descriptor, check our aggregate 
        // descriptors now.
        for (Iterator mapIt = m_aggregateDmds.iterator(); mapIt.hasNext(); ) {
        	MetadataDescriptor aggregateDmd = (MetadataDescriptor) mapIt.next();
            DatabaseMapping mapping = aggregateDmd.getMappingForAttributeName(attributeName);
            
            if (mapping != null) {
                return mapping;
            }
        }
        
        // We didn't find a mapping on the aggregate descriptors. If we are an
        // inheritance subclass, check for a mapping on the inheritance root
        // descriptor metadata.
        if (isInheritanceSubclass()) {
            return getInheritanceRootDmd().getMappingForAttributeName(attributeName);
        }
        
        // Found nothing ... return null.
        return null;
    } 
    
    /**
     * INTERNAL:
     */
    public List getMappings() {
        return m_descriptor.getMappings();
    }
    
    /**
     * INTERNAL:
     */
    public String getPKClassName() {
        String pkClassName = null;
        
        if (m_descriptor.hasCMPPolicy()) {
            pkClassName = ((CMP3Policy) m_descriptor.getCMPPolicy()).getPKClassName();    
        }
        
        return pkClassName;
    }
    
    /**
     * INTERNAL:
	 * Method to return the primary key field for the given descriptor
     * metadata. Assumes there is one and only one.
     */
    public String getPrimaryKeyFieldName() {
        // This must be here for an override feature otherwise it shouldn't be
        // necessary. WIP - investigate.
    	if (getPrimaryKeyFields() == null || !(getPrimaryKeyFields().iterator().hasNext())) {
    		return "";
    	}
        
        return ((DatabaseField)(getPrimaryKeyFields().iterator().next())).getName();
    }
    
    /**
     * INTERNAL:
     * Return the primary key fields for this descriptor metadata. If this is
     * an inheritance subclass and it has no primary key fields, then grab the 
     * primary key fields from the root.
     */
    public List getPrimaryKeyFields() {
        List primaryKeyFields = m_descriptor.getPrimaryKeyFields();
        
        if (primaryKeyFields.isEmpty() && isInheritanceSubclass()) {
            primaryKeyFields = getInheritanceRootDmd().getPrimaryKeyFields();
        }
        
        return primaryKeyFields;
    }
    
    /**
     * INTERNAL:
     */
	public String getPrimaryTableName() {
        // Assumes tables have been specified via XML and that the first
        // table is the primary table.
        Vector tables = m_descriptor.getTables();
        
        if (m_primaryTableName == null && !tables.isEmpty()) {
            m_primaryTableName = ((DatabaseTable) tables.firstElement()).getQualifiedName();
        } else if (m_primaryTableName == null && tables.isEmpty()) {
        	m_primaryTableName = "";
        }
        
		return m_primaryTableName;
	}
    
    /**
     * INTERNAL:
     */
    public Collection getRelationshipAccessors() {
        return m_relationshipAccessors.values();
    }
    
    /**
     * INTERNAL:
     * The value for m_schema will be one of:
     * 	- persistence unit default
     * 	- entity-mappings default
     * 	- empty string
     */
    public String getSchema() {
    	return m_xmlSchema;
    }

    /**
     * INTERNAL:
     */
    public String getSetMethodName(String getMethodName) {
        return (String) m_setMethodNames.get(getMethodName);
    }
    
    /**
     * INTERNAL:
     */
    public abstract void handlePotentialDefaultTableUsage(DatabaseField dbField);

    /**
     * INTERNAL:
     */
    public boolean hasAssociationOverrideFor(MetadataAccessor accessor) {
        return m_associationOverrides.containsKey(accessor.getAttributeName());
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasAttributeOverrideFor(MetadataAccessor accessor) {
        return hasAttributeOverrideFor(accessor.getAttributeName());
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasAttributeOverrideFor(String attributeName) {
        return m_attributeOverrides.containsKey(attributeName);
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasCompositePrimaryKey() {
        return getPrimaryKeyFields().size() > 1 || getPKClassName() != null;
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasEmbeddedIdAttribute() {
        return m_embeddedIdAttributeName != null;
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasEntityEventListener() {
        return m_descriptor.getEventManager().hasEntityEventListener();
    }
    
    /**
     * INTERNAL:
     */
    public boolean hasEntityListenerEventListeners() {
        return m_descriptor.getEventManager().hasEntityListenerEventListeners();
    }
    
    /**
     * INTERNAL:
     */
    protected abstract boolean hasEntityTag(Class cls);
    
    /**
     * INTERNAL:
     */
    protected abstract boolean hasInheritanceTag(Class entityClass);
    
    /**
     * INTERNAL:
     */
    public boolean hasManyToManyAccessorFor(Class cls) {
        return m_manyToManyAccessors.containsKey(cls);
    }
    
    /**
     * INTERNAL:
     */
    protected abstract boolean hasMappedSuperclassTag(Class cls);
    
    /**
     * INTERNAL:
     */
    public boolean hasMappingForAccessor(MetadataAccessor accessor) {
        return hasMappingForAttributeName(accessor.getAttributeName());
    }

    /**
     * INTERNAL:
     */
    public boolean hasMappingForAttributeName(String attributeName) {
        return m_descriptor.getMappingForAttributeName(attributeName) != null;
    }
    
    /**
     * INTERNAL:
     * Returns a list of mapped superclasses for this metadata descriptor.
     */
	public List getMappedSuperclasses() {
        if (m_mappedSuperclasses == null) {
            m_mappedSuperclasses = new ArrayList();
            
            Class parent = m_javaClass.getSuperclass();
        
            while (parent != Object.class) {
            	if (hasMappedSuperclassTag(parent)) {
                    m_mappedSuperclasses.add(parent);
                }
                
                parent = parent.getSuperclass();
            }
        }
            
        return (ArrayList) m_mappedSuperclasses;
    }
    
    /**
     * INTERNAL:
	 * Return true is the descriptor has primary key fields set.
     */
	public boolean hasPrimaryKeyFields() {
		return m_descriptor.getPrimaryKeyFields().size() > 0;
    }
    
	/**
	 * INTERNAL:
	 */
	public abstract boolean hasPrimaryKeyJoinColumns();

    
    /**
     * INTERNAL:
     */
    public boolean ignoreIDAnnotations() {
        return m_ignoreIDAnnotations;    
    }
    
    /**
     * INTERNAL:
     */
    public boolean ignoreInheritanceAnnotations() {
        return m_ignoreInheritanceAnnotations;    
    }
    
    /**
     * INTERNAL:
     */
    public boolean ignoreTableAnnotations() {
        return m_ignoreTableAnnotations;    
    }
    
    /**
     * INTERNAL:
     */
    private void init() {
        m_isInheritanceRoot = false;
        m_ignoreIDAnnotations = false;
        m_ignoreTableAnnotations = false;
        m_ignoreInheritanceAnnotations = false;
        
        m_pkClassIDs = new HashMap();
        m_accessors = new HashMap();
        m_setMethodNames = new HashMap();

        m_idAttributeNames = new ArrayList();
        m_orderByAttributeNames = new ArrayList();
        m_idOrderByAttributeNames = new ArrayList();
        m_aggregateDmds = new ArrayList();
        
        m_attributeOverrides = new HashMap();
        m_associationOverrides = new HashMap();
        m_manyToManyAccessors = new HashMap();
        m_relationshipAccessors = new HashMap();        

        // initialize values set during XML processing
        m_isXmlPropertyAccess = null;
        m_isCascadePersistSet = null;
        m_xmlExcludeDefaultListeners = false;
        m_xmlExcludeSuperclassListeners = false;
        m_ignoreAnnotations = false;
        m_xmlCatalog = "";
        m_xmlSchema = "";
        m_isDefaultPrimaryKeySet = false;
        m_isDefaultPrimaryTableSet = false;
    }
    
    /**
     * INTERNAL:
     * If "set", indicates that cascade-persist should be applied to all 
     * relationship mappings for this entity.
     */
    public boolean isAggregate() {
    	return m_descriptor.isAggregateDescriptor();
    }
    
    /**
     * INTERNAL:
     * If "set", indicates that cascade-persist should be applied to all 
     * relationship mappings for this entity.
     */
    public Boolean isCascadePersistSet() {
    	return m_isCascadePersistSet != null;
    }
        
    /**
     * INTERNAL:
     */
    public boolean isDefaultPrimaryKeySet() {
    	return m_isDefaultPrimaryKeySet;
    }
    
    /**
     * INTERNAL:
     */
    public boolean isDefaultPrimaryTableSet() {
    	return m_isDefaultPrimaryTableSet;
    }
    
    /**
     * INTERNAL:
     * If "set", indicates that default listeners in the m_defaultListenerList 
     * list NOT should be applied to the entity.
     */
    public boolean isXmlExcludeDefaultListenersSet() {
    	return m_xmlExcludeDefaultListeners;
    }
    
    /**
     * INTERNAL:
     * If "set", indicates that superclass listeners should NOT be applied to 
     * the entity.
     */
    public boolean isXmlExcludeSuperclassListenersSet() {
    	return m_xmlExcludeSuperclassListeners;
    }
    
    /**
     * INTERNAL:
     * Indicates that we found an XML field access type for this metadata
     * descriptor.
     */
    public boolean isXmlFieldAccess() {
        return (m_isXmlPropertyAccess != null && m_isXmlPropertyAccess.booleanValue() == false);
    }
    
    /**
     * INTERNAL:
     * Indicates that we found an XML property access type for this metadata
     * descriptor.
     */
    public boolean isXmlPropertyAccess() {
    	return (m_isXmlPropertyAccess != null && m_isXmlPropertyAccess.booleanValue() == true);
    }
    
    /**
     * INTERNAL:
     * Return true is this descriptor metadata is an inheritance root.
     */
    public boolean isInheritanceRoot() {
        return m_isInheritanceRoot;
    }
    
    /**
     * INTERNAL:
     * Method to determine if the given class is an inheritance subclass.
     */
    public boolean isInheritanceSubclass() {
        return (getInheritanceRootClass() != null);
    }
    
    /**
     * INTERNAL:
     * Calls to this method should only be made once to determine if this
     * descriptor metadata is part of an inhertiance hierarchy. Any consecutive
     * inquiries should be made through the isInheritanceSubclass() call.
     */
    protected boolean isInheritanceSubclass(HashMap m_metadataDescriptors) {
        Class lastParent = null;
        Class parent = m_javaClass.getSuperclass();
        
        while (parent != Object.class) {
            if (hasInheritanceTag(parent) || m_metadataDescriptors.containsKey(parent) || hasEntityTag(parent)) {
                lastParent = parent;
            }
                
            parent = parent.getSuperclass();
        }
        
        // Finally set whatever we found as the inheritance root class. Which
        // may be null.
        m_inheritanceRootClass = lastParent;

        return isInheritanceSubclass();
    }

    /**
     * INTERNAL:
     */
    public boolean pkClassWasNotValidated() {
        return ! m_pkClassIDs.isEmpty();
    }

    /**
     * INTERNAL:
     */
    public void setAlias(String alias) {
        m_descriptor.setAlias(alias);
    }
    
    /**
     * INTERNAL:
     */
    public void setCatalog(String xmlCatalog) {
    	m_xmlCatalog = xmlCatalog;
    }

    /**
     * INTERNAL:
     */
    public void setClassIndicatorField(DatabaseField field) {
        m_descriptor.getInheritancePolicy().setClassIndicatorField(field);    
    }
    
    /**
     * INTERNAL:
     */
    public void setDefaultListeners(List defaultListeners) {
    	m_defaultListeners = defaultListeners;
    }
    
    /**
     * INTERNAL:
     */
    public void setDefaultPrimaryKey() {
    	m_isDefaultPrimaryKeySet = true;
    }
    
    /**
     * INTERNAL:
     */
    public void setDefaultPrimaryTable() {
    	m_isDefaultPrimaryTableSet = true;
    }
    
    /**
     * INTERNAL:
     */
	public void setDescriptor(RelationalDescriptor descriptor) {
        m_descriptor = descriptor;
    }
    
    /**
     * INTERNAL:
     */
    public void setDescriptorIsEmbeddable() {
        m_descriptor.descriptorIsAggregate();
    }
    
    /**
     * INTERNAL:
     */
    public void setEmbeddedIdAttributeName(String embeddedIdAttributeName) {
        m_embeddedIdAttributeName = embeddedIdAttributeName;
    }
    
    /**
     * INTERNAL:
     */
    public void setExcludeDefaultListeners(boolean excludeDefaultListeners) {
        m_descriptor.getEventManager().setExcludeDefaultListeners(excludeDefaultListeners);
    }
     
    /**
     * INTERNAL:
     */
    public void setExcludeSuperclassListeners(boolean excludeSuperclassListeners) {
        m_descriptor.getEventManager().setExcludeSuperclassListeners(excludeSuperclassListeners);
    }
    
    /**
     * INTERNAL:
     */
    public void setIgnoreFlags() {
        m_ignoreInheritanceAnnotations = m_descriptor.hasInheritance();
        m_ignoreTableAnnotations = m_descriptor.getTableNames().size() > 0;
        m_ignoreIDAnnotations = m_descriptor.getPrimaryKeyFieldNames().size() > 0 && !isDefaultPrimaryKeySet();
    }
    
    /**
     * INTERNAL:
     * Store the descriptor metadata for the root of our inheritance hierarchy.
     */
    public void setInheritanceRootDmd(MetadataDescriptor inheritanceRootDmd) {
        m_inheritanceRootDmd = inheritanceRootDmd;
    }
    
    /**
     * INTERNAL:
     * Store the java class for the root of our inheritance hierarchy.
     */
	public void setInheritanceRootClass(Class rootClass) {
		m_inheritanceRootClass = rootClass;
	}
	
    /**
     * INTERNAL:
     * Stored on the root class of an inheritance hierarchy.
     */
    public void setInheritanceStrategy(String inheritanceStrategy) {
        if (inheritanceStrategy.equals(MetadataConstants.TABLE_PER_CLASS)) {
            throw ValidationException.tablePerClassInheritanceNotSupported(m_javaClass);
        }
        
    	m_usesSingleTableInheritanceStrategy = (inheritanceStrategy.equals(MetadataConstants.SINGLE_TABLE));
    }
    
    /**
     * INTERNAL:
     * Set this descriptor metadata as an inheritance root. Because the 
     * inheritance root class may or may not have an @Inheritance annotation 
     * defined then we need to ensure that descriptor metadata knows it is.
     */
    public void setIsInheritanceRoot(boolean isInheritanceRoot) {
        m_isInheritanceRoot = isInheritanceRoot;
    }
    
    /**
     * INTERNAL:
     * Indicates that we found an XML field access type for this metadata
     * descriptor.
     */
    public void setIsXmlFieldAccess() {
    	m_isXmlPropertyAccess = new Boolean(false);
    }
    
    /**
     * INTERNAL:
     * Indicates that we found an XML property access type for this metadata
     * descriptor.
     */
    public void setIsXmlPropertyAccess() {
    	m_isXmlPropertyAccess = new Boolean(true);
    }

    /**
     * INTERNAL:
     * Used to set this descriptors java class. 
     */
    public void setJavaClass(Class javaClass) {
        m_javaClass = javaClass;
        
        if ((m_javaClass != null) && (m_descriptor != null)){
            m_descriptor.setJavaClassName(m_javaClass.getName());
        } else if (m_descriptor != null) {
            m_descriptor.setJavaClassName(null);
        }
    }
    
    /**
     * INTERNAL:
     */
    public void setOptimisticLockingPolicy(DatabaseField field) {
        VersionLockingPolicy vlp = new VersionLockingPolicy();
		vlp.setWriteLockField(field);
        vlp.storeInObject();
        m_descriptor.setOptimisticLockingPolicy(vlp);
    }
    
    /**
     * INTERNAL:
     * Should only be called from those descriptor metadata's that are in
     * fact part of an inheritance hierarchy. It figures out the parent and
     * then sets it.
     */
    public void setParentClass() {
        Class parent = m_javaClass.getSuperclass();
        
        while (parent != Object.class) {
            if (hasEntityTag(parent)) {
                break;
            }
            
            parent = parent.getSuperclass();
        }
        
        setParentClass(parent);
    }
    
    /**
     * INTERNAL:
     * Set the inheritance parent class for this descriptor metadata.
     */
    public void setParentClass(Class parent) {
        m_descriptor.getInheritancePolicy().setParentClassName(parent.getName());
    }
    
    /**
     * INTERNAL:
     */
    public void setPKClass(Class pkClass) {
        CMP3Policy policy = new CMP3Policy();
        policy.setPrimaryKeyClassName(pkClass.getName());
        m_descriptor.setCMPPolicy(policy);
    }
    
    /**
     * INTERNAL:
     */
	public void setPrimaryTable(DatabaseTable primaryTable) {
		Vector tables = m_descriptor.getTables();
        
		if (tables != null && !tables.isEmpty()) {
			tables.remove(0);
		}
        
		tables.add(0, primaryTable);
		m_primaryTableName = primaryTable.getQualifiedName();
	}

	/**
	 * INTERNAL:
	 */
	public void setSchema(String xmlSchema) {
    	m_xmlSchema = xmlSchema;
    }

    /**
     * INTERNAL:
     */
    public void setSequenceNumberField(DatabaseField field) {
        DatabaseField existingField = m_descriptor.getSequenceNumberField();
        if (existingField == null) {
            m_descriptor.setSequenceNumberField(field);
        } else {
            if (!existingField.equals(field)) {
                throw ValidationException.onlyOneGeneratedValueIsAllowed(m_javaClass, existingField.getQualifiedName(), field.getQualifiedName());
            }
        }
    }
    
    /**
     * INTERNAL:
     */
    public void setSequenceNumberName(String name) {
        m_descriptor.setSequenceNumberName(name);
    }

    /**
     * INTERNAL:
     * Indicates that default listeners are NOT to be applied to the entity.
     */
    public void setXmlExcludeDefaultListeners(boolean xmlExcludeDefaultListeners) {
    	m_xmlExcludeDefaultListeners = xmlExcludeDefaultListeners;
    }

    /**
     * INTERNAL:
     * Indicates that superclass listeners are NOT to be applied to the entity.
     */
    public void setXmlExcludeSuperclassListeners(boolean xmlExcludeSuperclassListeners) {
    	m_xmlExcludeSuperclassListeners = xmlExcludeSuperclassListeners;
    }
    
    /**
     * INTERNAL:
     * Indicates that all annotations should be ignored, and only default values 
     * set by the annotations processor.
     */
    public void setShouldIgnoreAnnotations(boolean ignoreAnnotations) {
    	m_ignoreAnnotations = ignoreAnnotations;
    }

    /**
     * INTERNAL:
     * Indicates that cascade-persist should be added to the set of cascade 
     * values for all relationship mappings.
     */
    public void setShouldUseCascadePersist() {
    	m_isCascadePersistSet = true;
    }
    
    /**
     * INTERNAL:
     * Sets the strategy on the descriptor's inheritance policy to SINGLE_TABLE.  
     * The default is JOINED.
     */
    public void setSingleTableInheritanceStrategy() {
        m_descriptor.getInheritancePolicy().setSingleTableStrategy();
    }

    /**
     * INTERNAL:
     * Indicates whether or not annotations should be ignored, i.e. only default 
     * values processed.
     */
    public boolean shouldIgnoreAnnotations() {
    	return m_ignoreAnnotations;
    }
    
    /**
     * INTERNAL:
     */
    public boolean usesOptimisticLocking() {
        return m_descriptor.usesOptimisticLocking();
	}
    
    /**
     * INTERNAL:
     * Returns true if this entity uses property access.
     */
	public abstract boolean usesPropertyAccess();
    
    /**
     * INTERNAL:
     * Indicates if the strategy on the descriptor's inheritance policy is 
     * SINGLE_TABLE or JOINED.
     * 
     * @return true if SINGLE_TABLE, false otherwise
     */
    public boolean usesSingleTableInheritanceStrategy() {
        return m_usesSingleTableInheritanceStrategy;
    }
    
    /**
     * INTERNAL:
     * This method is used only to validate id fields that were found on a
     * pk class were also found on the entity.
     */
    public void validatePKClassId(String attributeName, Class type) {
        if (m_pkClassIDs.containsKey(attributeName))  {
            Class value = (Class) m_pkClassIDs.get(attributeName);
            
            if (value == type) {
                m_pkClassIDs.remove(attributeName);
            } else {
                throw ValidationException.invalidCompositePKAttribute(m_javaClass, getPKClassName(), attributeName, value, type);
            }
        }
    }
}