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

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.exolab.castor.mapping.AbstractFieldHandler;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.xml.FieldValidator;
import org.exolab.castor.xml.NodeType;
import org.exolab.castor.xml.TypeValidator;
import org.exolab.castor.xml.UnmarshalState;
import org.exolab.castor.xml.ValidationContext;
import org.exolab.castor.xml.ValidationException;
import org.exolab.castor.xml.Validator;
import org.exolab.castor.xml.XMLClassDescriptor;
import org.exolab.castor.xml.XMLFieldDescriptor;
import org.exolab.castor.xml.location.XPathLocation;
import org.exolab.castor.xml.util.resolvers.ResolveHelpers;

/**
 * The core implementation of XMLClassDescriptor. This class is used by both
 * generated source code as well as the XMLMappingLoader.
 *
 * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
 * @version $Revision: 8734 $ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
 */
public class XMLClassDescriptorImpl extends Validator implements XMLClassDescriptor {
    /** The ALL compositor to signal the fields of the described class must all
     *  be present and valid, if they are required. */
    private static final short ALL = 0;

    /** The CHOICE compositor to signal the fields of the described class must be
     *  only a choice. They are mutually exclusive. */
    private static final short CHOICE = 1;

    /** The SEQUENCE compositor....currently is the same as ALL. */
    private static final short SEQUENCE = 2;


    private static final String NULL_CLASS_ERR = 
        "The Class passed as an argument to the constructor of "
        + "XMLClassDescriptorImpl may not be null.";

    private static final String WILDCARD = "*";

    /** The set of attribute descriptors. */
    private XMLFieldDescriptors _attributes = null;

    /** Cached attribute descriptors for improved performance. */
    private XMLFieldDescriptor[] _attArray = null;

    /** The Class that this ClassDescriptor describes. */
    private Class<?> _class = null;

    /** A variable to keep track of the number of container fields. */
    private int _containerCount = 0;

    /** The XMLFieldDescriptor for text data. */
    private XMLFieldDescriptor _contentDescriptor = null;

    /** The TypeValidator to use for validation of the described class. */
    private TypeValidator _validator = null;

    /** The set of element descriptors. */
    private XMLFieldDescriptors _elements = null;

    /** Cached element descriptors for improved performance. */
    private XMLFieldDescriptor[] _elemArray = null;

    /** The namespace prefix that is to be used when marshalling. */
    private String _nsPrefix = null;

    /** The namespace URI used for both Marshalling and Unmarshalling. */
    private String _nsURI = null;

    /** The name of the XML element. */
    private String  _xmlName;

    /** Flag to indicate XML refers to global element or element with
     *  anonymous type in XSD. Useful information for frameworks
     *  that use Castor for XML binding and generate additional schema
     *  definition elements. */
    private boolean _elementDefinition = false;

    /** The descriptor of the class which this class extends,
     *  or null if this is a top-level class. */
    private XMLClassDescriptor _extends;

    /** The field of the identity for this class. */
    private FieldDescriptor _identity;

    /** The access mode specified for this class. */
    private AccessMode _accessMode;

    /** A flag to indicate that this XMLClassDescriptor was
     *  created via introspection. */
    private boolean _introspected = false;

    private short _compositor = ALL;
    
    /** Defines the sequence of elements for unmarshalling validation
     *  (to be used with compositor == SEQUENCE only). */
    private List<XMLFieldDescriptor> _sequenceOfElements = new ArrayList<XMLFieldDescriptor>();
    
    private List<String> _substitutes = new LinkedList<String>();    

    /** Map holding the properties set and read by Natures. */
    private Map<String, Object> _properties = new HashMap<String, Object>();
    
    /** Map holding the available natures. */
    private Set<String> _natures = new HashSet<String>();

    /**
     * Creates an XMLClassDescriptor class used by the Marshalling Framework.
     *
     * @param type the Class type with which this ClassDescriptor describes.
     */
    public XMLClassDescriptorImpl(final Class<?> type) {
        this();
        if (type == null) {
            throw new IllegalArgumentException(NULL_CLASS_ERR);
        }

        _class = type;
        // useless, no name is known setXMLName(null);
    }

    /**
     * Creates an XMLClassDescriptor class used by the Marshalling Framework.
     *
     * @param type the Class type with which this ClassDescriptor describes.
     */
    public XMLClassDescriptorImpl(Class<?> type, String xmlName) {
        this();

        if (type == null) {
            throw new IllegalArgumentException(NULL_CLASS_ERR);
        }

        this._class = type;
        setXMLName(xmlName);
    } //-- XMLClassDescriptorImpl

    /**
     * Protected constructor used by this class, and subclasses only
     */
    protected XMLClassDescriptorImpl() {
        _attributes = new XMLFieldDescriptors(5);
        _elements = new XMLFieldDescriptors(7);
    } //-- XMLClassDescriptor

    //------------------/
    //- Public Methods -/
    //------------------/

    /**
     * Adds the given XMLFieldDescriptor to the list of descriptors. The
     * descriptor will be added to the appropriate list by calling
     * XMLFieldDescriptor#getNodeType() to determine it's type.
     *
     * @param descriptor the XMLFieldDescriptor to add
     */
    public void addFieldDescriptor(XMLFieldDescriptor descriptor) {
        addFieldDescriptor(descriptor, true);
    } //-- addFieldDescriptor

    /**
     * Returns true if the given XMLFieldDescriptor is contained
     * within this XMLClassDescriptor.
     *
     * @return true if the XMLFieldDescriptor is part of this
     * XMLClassDescriptor, otherwise false.
     */
    public boolean contains(XMLFieldDescriptor descriptor) {
        if (descriptor == null) {
            return false;
        }

        if (_attributes.contains(descriptor)) {
            return true;
        }
        if (_elements.contains(descriptor)) {
            return true;
        }

        return descriptor.equals(_contentDescriptor);
    } //-- contains

    /**
     * Returns the set of XMLFieldDescriptors for all members that should be
     * marshalled as XML attributes.
     *
     * @return an array of XMLFieldDescriptors for all members that should be
     *         marshalled as XML attributes.
     */
    public XMLFieldDescriptor[]  getAttributeDescriptors() {
        return getAttributeArray().clone();
    } // getAttributeDescriptors

    /**
     * Returns the XMLFieldDescriptor for the member that should be marshalled
     * as text content.
     *
     * @return the XMLFieldDescriptor for the member that should be marshalled
     *         as text content.
     */
    public XMLFieldDescriptor getContentDescriptor() {
        return _contentDescriptor;
    } // getContentDescriptor

    /**
     * Returns the set of XMLFieldDescriptors for all members
     * that should be marshalled as XML elements.
     *
     * @return an array of XMLFieldDescriptors for all members
     * that should be marshalled as XML elements.
     */
    public XMLFieldDescriptor[]  getElementDescriptors() {
        return getElementArray().clone();
    } // getElementDescriptors
    
    /**
     * Checks whether the given XMLFieldDescriptor is the one actually expected,
     * given the natural order as defined by a sequence definition
     * @param elementDescriptor The XML field descriptor to be checked
     * @throws ValidationException If the descriptor is not the one expected
     */
    public void checkDescriptorForCorrectOrderWithinSequence(
            final XMLFieldDescriptor elementDescriptor, UnmarshalState parentState, String xmlName) throws ValidationException {
        if (_compositor == SEQUENCE && _sequenceOfElements.size() > 0) {
        	
        	if (parentState.getExpectedIndex() == _sequenceOfElements.size() ) {
        		throw new ValidationException ("Element with name " + xmlName + " passed to type " + getXMLName() + " in incorrect order; It is not allowed to be the last element of this sequence!");
        	}
        	
        	XMLFieldDescriptor expectedElementDescriptor = _sequenceOfElements.get(parentState.getExpectedIndex());
            
            String expectedElementName = expectedElementDescriptor.getXMLName();
            String elementName = xmlName;

            boolean anyNode = expectedElementDescriptor.getFieldName().equals("_anyObject") && expectedElementName == null;
            
            // choices
            if (!anyNode && expectedElementDescriptor.getXMLName().equals("-error-if-this-is-used-")) {
            	
            	// find possible names
            	List<String> possibleNames = new ArrayList<String>();
            	fillPossibleNames(possibleNames, expectedElementDescriptor);
            	
            	// check name
            	if (!possibleNames.contains(elementName)) {
            		if (!expectedElementDescriptor.isRequired()) {
            			 parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
                         checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
            		} else {
            			throw new ValidationException ("Element with name " + elementName + " passed to type " + getXMLName() + " in incorrect order; expected element has to be member of the expected choice.");
            		}
            	} else {
            		parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
            	}
                return;
            }

            // multi valued flag
            if (expectedElementDescriptor.isMultivalued() && !parentState.isWithinMultivaluedElement()) {
            	parentState.setWithinMultivaluedElement(true);
            }
            
            if (!anyNode && !(expectedElementName).equals(elementName)) {
                
                // handle substitution groups !!! 
                List<String> substitutes =  expectedElementDescriptor.getSubstitutes();
                if (substitutes != null && !substitutes.isEmpty()) {
                    if (substitutes.contains(elementName)) {
                        if (!parentState.isWithinMultivaluedElement()) {
                            parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
                        }
                        return;
                    }
                }
                // handle multi-valued fields
                if (expectedElementDescriptor.isMultivalued()) {
                	parentState.setWithinMultivaluedElement(false);
                    parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
                    checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
                    return;
                }
                // handle required fields
                if (expectedElementDescriptor.isRequired()) {
                    throw new ValidationException ("Element with name " + elementName + " passed to type " + getXMLName() + " in incorrect order; expected element with name '" + expectedElementName + "' or any other optional element declared prior to it.");
                }
                
                // non required field, proceed until next required field
                parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
                checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
                return;
                
            }
            
            if (!parentState.isWithinMultivaluedElement()) {
                parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
            }
        }
   }
    
    
    private void fillPossibleNames(List<String> possibleNames, XMLFieldDescriptor descriptor) {
    	XMLFieldDescriptor[] descriptors = ((XMLClassDescriptor)descriptor.getClassDescriptor()).getElementDescriptors();
    	if (descriptors.length == 0) {
    		return;
    	}
    	for (XMLFieldDescriptor fieldDescriptor : descriptors) {
    	    if ("_items".equals(fieldDescriptor.getFieldName() ) 
    	            || "-error-if-this-is-used-".equals(fieldDescriptor.getXMLName())) {
    	        fillPossibleNames(possibleNames, fieldDescriptor);
    	    } else {
    	        possibleNames.add(fieldDescriptor.getXMLName());
    	    }
        }
    }

    /**
     * Returns the XML field descriptor matching the given xml name and
     * nodeType. If NodeType is null, then either an AttributeDescriptor, or
     * ElementDescriptor may be returned. Null is returned if no matching
     * descriptor is available.
     * <p>
     * If an field is matched in one of the container field, it will return the
     * container field that contain the field named 'name'
     *
     * @param name the xml name to match against
     * @param nodeType the NodeType to match against, or null if the node type
     *        is not known.
     * @return the matching descriptor, or null if no matching descriptor is
     *         available.
     */
    public XMLFieldDescriptor getFieldDescriptor(String name, String namespace,
            NodeType nodeType) {
        boolean wild = ((nodeType == null) || _introspected);
        XMLFieldDescriptor result = null;

        XMLFieldDescriptor[] attributes = _attArray;
        XMLFieldDescriptor[] elements   = _elemArray;

        // TODO: clean up location patch
        String location = null;
        if (name != null) {
            int idx = name.lastIndexOf('/');
            if (idx >= 0) {
                location = name.substring(0, idx);
                name = name.substring(idx+1);
            }
        }

        if (wild || (nodeType == NodeType.Element)) {

            if (elements == null) elements = getElementArray();

//            if (_compositor == SEQUENCE && sequenceOfElements.size() > 0) {
//                XMLFieldDescriptor elementDescriptor = (XMLFieldDescriptor) sequenceOfElements.get(sequenceElementCount);
//                String elementName = elementDescriptor.getXMLName();
//                if (!elementName.equals(name)) {
//                    throw new IllegalArgumentException ("Element with name " + name + " passed to type " + getXMLName() + " in incorrect order; expected TODO.");
//                } else {
//                    sequenceElementCount++;
//                }
//            }
            
            for (int i = 0; i < elements.length; i++) {
                XMLFieldDescriptor desc = elements[i];
                
                if (desc == null) continue;

                if (location != null) {
                    if (!location.equals(desc.getLocationPath()))
                        continue;
                }

                if (desc.matches(name)) {
                      if (!desc.matches(WILDCARD)) {
                          // currently, a 'null' namespace value indicates that the XML artifact in question
                          // either is namespace-less or is part of the default namespace; in both cases, 
                          // we currently can not perform namespace comparison.
                          // TODO[wguttmn, 20071205]: add code to handle default namespace declarations rather than checking for null
                          if (namespace == null 
                                  || ResolveHelpers.namespaceEquals(namespace, desc.getNameSpaceURI())) {
                              return desc;
                          }
                      } else {
                          //--  possible wildcard match check for
                          //--  exact match (we need to extend the
                          //--  the descriptor interface to handle this
                          if (name.equals(desc.getXMLName()))
                              return desc;
                          //-- assume wild-card match
                          result = desc;
                      }
                }

                //handle container
                if ( desc.isContainer() ) {
                    XMLClassDescriptor xcd = (XMLClassDescriptor)desc.getClassDescriptor();
                    //prevent endless loop
                    if (xcd != this) {
                        //is it in this class descriptor?
                        if (xcd.getFieldDescriptor(name, namespace, NodeType.Element) != null) {
                            result = desc;
                            break;
                        }
                    }
                }//container
            }

            if (result != null)
                return result;
        }

        //-- handle attributes
        if (wild || (nodeType == NodeType.Attribute))
        {
            if (attributes == null) attributes = getAttributeArray();
            for (int i = 0; i < attributes.length; i++) {
                XMLFieldDescriptor desc = attributes[i];
                if (desc == null) continue;
                if (desc.matches(name)) {
                    return desc;
                }
            }
        }

        //-- handle namespace node
        if (nodeType == NodeType.Namespace) {
            if (attributes == null) attributes = getAttributeArray();
            for (int i = 0; i < attributes.length; i++) {
                if (attributes[i] == null) continue;
                if (attributes[i].getNodeType() == NodeType.Namespace) {
                    return attributes[i];
                }
            }
        }

        // To handle container object, we need to check if an attribute of a
        // container field match this attribute
        if (nodeType == NodeType.Attribute) {
            if (elements == null) elements = getElementArray();
            for (int i = 0; i < elements.length; i++) {
                XMLFieldDescriptor desc = elements[i];
                if (desc.isContainer()) {
                    XMLClassDescriptor xcd = (XMLClassDescriptor)desc.getClassDescriptor();
                    //prevent endless loop
                    if (xcd != this) {
                        //is it in this class descriptor?
                        XMLFieldDescriptor temp = xcd.getFieldDescriptor(name, namespace, NodeType.Attribute);
                        if (temp != null) {
                            return desc;
                        }
                    }
                }
            }
        }

        return null;

    } //-- getFieldDescriptor


    /**
     * @return the namespace prefix to use when marshalling as XML.
     */
    public String getNameSpacePrefix() {
        return _nsPrefix;
    } //-- getNameSpacePrefix

    /**
     * @return the namespace URI used when marshalling and unmarshalling as XML.
     */
    public String getNameSpaceURI() {
        return _nsURI;
    } //-- getNameSpaceURI


    /**
     * Returns a specific validator for the class described by
     * this ClassDescriptor. A null value may be returned
     * if no specific validator exists.
     *
     * @return the type validator for the class described by this
     * ClassDescriptor.
     */
    public TypeValidator getValidator() {
        if (_validator != null)
            return _validator;
        return this;
    } //-- getValidator

    /**
     * Returns the XML Name for the Class being described.
     *
     * @return the XML name.
     */
    public String getXMLName() {
        return _xmlName;
    } //-- getXMLName

    /**
     * Returns true if XML schema definition of this Class is that of a global
     * element or element with anonymous type definition.
     */
    public boolean isElementDefinition() {
            return _elementDefinition;
    } //-- isElementDefinition

    /**
     * Returns true if this XMLClassDescriptorImpl has any fields which are
     * container objects. A container object is a Java object which holds
     * data the should be marshalled, but the object itself should not be.
     * So the container object will be "unwrapped" and the fields
     * associated with the container will appear as if they were part
     * of this class.
     *
     * @return true if any of the fields are container fields, otherwise false.
     */
    public boolean hasContainerFields() {
        return (_containerCount > 0);
    } //-- hasContainerFields

    /**
     * Removes the given XMLFieldDescriptor from the list of descriptors.
     *
     * @param descriptor the XMLFieldDescriptor to remove
     * @return true if the descriptor was removed.
     */
    public boolean removeFieldDescriptor(XMLFieldDescriptor descriptor) {

        if (descriptor == null) return false;

        boolean removed = false;
        NodeType nodeType = descriptor.getNodeType();
        switch(nodeType.getType()) {
            case NodeType.NAMESPACE:
            case NodeType.ATTRIBUTE:
                if (_attributes.remove(descriptor)) {
                    removed = true;
                    _attArray = null;
                }
                break;
            case NodeType.TEXT:
                if (_contentDescriptor == descriptor) {
                    _contentDescriptor = null;
                    removed = true;
                }
                break;
            default:
                if (_elements.remove(descriptor)) {
                    _elemArray = null;
                    removed = true;
                    if (descriptor.isContainer()) --_containerCount;
                }
                break;
        }
        return removed;
    } //-- removeFieldDescriptor

    /**
     * Sets the compositor for the fields of the described
     * class to be ALL.
     */
    public void setCompositorAsAll() {
        _compositor = ALL;
    }  //-- setCompositorAsAll

    /**
     * Sets the compositor for the fields of the described
     * class to be CHOICE.
     */
    public void setCompositorAsChoice() {
        _compositor = CHOICE;
    }  //-- setCompositorAsChoice

    /**
     * Sets the compositor for the fields of the described
     * class to be a Sequence.
     */
    public void setCompositorAsSequence() {
        _compositor = SEQUENCE;
    }  //-- setCompositorAsSequence

    /**
     * Sets the XMLClassDescriptor that this descriptor inherits from
     * @param classDesc the XMLClassDescriptor that this descriptor
     * extends
     */
    public void setExtends(XMLClassDescriptor classDesc) {

        FieldDescriptor[] fields = null;
        //-- remove reference to previous extended descriptor
        if (_extends != null) {
            sortDescriptors();
            fields = _extends.getFields();
            for (int i = 0; i < fields.length; i++) {
                removeFieldDescriptor((XMLFieldDescriptor)fields[i]);
            }
        }

        this._extends = classDesc;

        //-- flatten out the hierarchy
        if (_extends != null) {
            fields = classDesc.getFields();
            for (int i = 0; i < fields.length; i++) {
                addFieldDescriptor((XMLFieldDescriptor)fields[i], false);
            }
        }

    } //-- setExtends

    /**
     * Sets the Identity FieldDescriptor, if the FieldDescriptor is
     * not already a contained in this ClassDescriptor, it will be
     * added
     */
    public void setIdentity(XMLFieldDescriptor fieldDesc) {
        if (fieldDesc != null) {
            if ( (! _attributes.contains(fieldDesc)) &&
                (! _elements.contains(fieldDesc))) {
                addFieldDescriptor(fieldDesc);
            }
        }
        this._identity = fieldDesc;
    } //-- setIdentity

    /**
     * Sets the namespace prefix used when marshalling as XML.
     *
     * @param nsPrefix the namespace prefix used when marshalling
     * the "described" object
     */
    public void setNameSpacePrefix(String nsPrefix) {
        this._nsPrefix = nsPrefix;
    } //-- setNameSpacePrefix

    /**
     * Sets the namespace URI used when marshalling and unmarshalling as XML.
     *
     * @param nsURI the namespace URI used when marshalling and
     * unmarshalling the "described" Object.
     */
    public void setNameSpaceURI(String nsURI) {
        this._nsURI = nsURI;
    } //-- setNameSpaceURI

    /**
     * Sets the validator to use for the class described by this
     * ClassDescriptor
     *
     * @param validator the validator to use when peforming validation
     * of the described class. This may be null to signal default
     * validation.
     */
    //public void setValidator(TypeValidator validator) {
    //    this.validator = validator;
    //} //-- setValidator

    /**
     * Sets the XML name for the Class described by this XMLClassDescriptor
     *
     * @param xmlName the XML name for the Class described by this
     * XMLClassDescriptor
     */
    public void setXMLName(String xmlName) {
        if (xmlName == null) {
            if (_xmlName == null && _class != null) {
                _xmlName = _class.getName();
            }
        }
        else this._xmlName = xmlName;
    } //-- setXMLName

    /**
     * Set elementDefinition to true to indicate Class XML schema
     * definition is a global element or element with anonymous type.
     *
     * @param elementDefinition flag to indicate XML definition is
     * global element or element with anonymous type
     */
    public void setElementDefinition(boolean elementDefinition){
            this._elementDefinition = elementDefinition;
    } //-- setElementDefinition

    /**
     * This method is used to keep the set of descriptors in the proper
     * sorted lists. If you dynamically change the NodeType of
     * an XMLFieldDescriptor after adding it the this ClassDescriptor,
     * then call this method.
     */
    public void sortDescriptors() {

        //-- handle attributes
        XMLFieldDescriptor[] descriptors = getAttributeArray();
        for (int i = 0; i < descriptors.length; i++) {
            XMLFieldDescriptor fieldDesc = descriptors[i];
            switch (fieldDesc.getNodeType().getType()) {
                case NodeType.ELEMENT:
                    _elements.add(fieldDesc);
                    _attributes.remove(fieldDesc);
                    _attArray = null;
                    break;
                case NodeType.TEXT:
                    _attributes.remove(fieldDesc);
                    _attArray = null;
                    break;
                default:
                    break;
            }
        }

        //-- handle elements
        descriptors = getElementArray();
        for (int i = 0; i < descriptors.length; i++) {
            XMLFieldDescriptor fieldDesc = descriptors[i];
            switch (fieldDesc.getNodeType().getType()) {
                case NodeType.NAMESPACE:
                case NodeType.ATTRIBUTE:
                    _attributes.add(fieldDesc);
                    _elements.remove(fieldDesc);
                    _elemArray = null;
                    break;
                case NodeType.TEXT:
                    _elements.remove(fieldDesc);
                    _elemArray = null;
                    break;
                default:
                    break;
            }
        }

    } //-- sortDescriptors

    /**
     * Returns the String representation of this XMLClassDescriptor
     *
     * @return the String representation of this XMLClassDescriptor
     */
    public String toString() {

        String str = super.toString() + "; descriptor for class: ";

        //-- add class name
        if (_class != null)
            str += _class.getName();
        else
            str += "[null]";

        //-- add xml name
        str += "; xml name: " + _xmlName;

        return str;
    } //-- toString

    /**
     * Validates the given Object
     *
     * @param object the Object to validate
     */
    public void validate(Object object)
        throws ValidationException
    {
        validate(object, (ValidationContext)null);
    } //-- validate

    /**
     * Validates the given object
     * @param object the Object to validate
     * @param context the ValidationContext
     */
    public void validate(Object object, ValidationContext context)
    throws ValidationException {
        if (object == null) {
            throw new ValidationException("Cannot validate a null object.");
        }
        Class<?> a = getJavaClass();
        ClassLoader acl = a.getClassLoader();
        Class<? extends Object> b = object.getClass();
        ClassLoader bcl = b.getClassLoader();
        if (!getJavaClass().isAssignableFrom(object.getClass())) {
            String err = "The given object is not an instance of the class"
                + " described by this ClassDecriptor.";
            throw new ValidationException(err);
        }

        //-- DEBUG
        //System.out.println("Validating class: " + object.getClass().getName());
        //-- /DEBUG

        XMLFieldDescriptor[] localElements = getElementArray();
        XMLFieldDescriptor[] localAttributes = getAttributeArray();

        if (_extends != null) {

            //-- cascade call for validation
            if (_extends instanceof XMLClassDescriptorImpl) {
                ((XMLClassDescriptorImpl) _extends).validate(object, context);
            } else {
                TypeValidator baseValidator = _extends.getValidator();
                if (baseValidator != null) {
                    baseValidator.validate(object, context);
                }
            }

            //-- get local element descriptors by filtering out inherited ones
            XMLFieldDescriptor[] inheritedElements   = _extends.getElementDescriptors();
            XMLFieldDescriptor[] allElements = localElements;
            localElements = new XMLFieldDescriptor[allElements.length - inheritedElements.length];
            int localIdx = 0;
            for (int i = 0; i < allElements.length; i++) {
                XMLFieldDescriptor desc = allElements[i];
                boolean isInherited = false;
                for (int idx = 0; idx < inheritedElements.length; idx++) {
                    if (inheritedElements[idx].equals(desc)) {
                        isInherited = true;
                        break;
                    }
                }
                if (!isInherited) {
                    localElements[localIdx] = desc;
                    ++localIdx;
                }
            }

            //-- get local attribute descriptors by filtering out inherited ones
            XMLFieldDescriptor[] inheritedAttributes = _extends.getAttributeDescriptors();
            XMLFieldDescriptor[] allAttributes = localAttributes;
            localAttributes = 
                new XMLFieldDescriptor[allAttributes.length - inheritedAttributes.length];
            localIdx = 0;
            for (int i = 0; i < allAttributes.length; i++) {
                XMLFieldDescriptor desc = allAttributes[i];
                boolean isInherited = false;
                for (int idx = 0; idx < inheritedAttributes.length; idx++) {
                    if (inheritedAttributes[idx].equals(desc)) {
                        isInherited = true;
                        break;
                    }
                }
                if (!isInherited) {
                    localAttributes[localIdx] = desc;
                    ++localIdx;
                }
            }

        }

        switch (_compositor) {

            case CHOICE:

                boolean found = false;
                boolean hasLocalDescs = (localElements.length > 0);
                XMLFieldDescriptor fieldDesc = null;
                //-- handle elements, affected by choice
                for (int i = 0; i < localElements.length; i++) {
                    XMLFieldDescriptor desc = localElements[i];

                    if (desc == null) {
                        continue;
                    }

                    FieldHandler handler = desc.getHandler();

                    if (handler.getValue(object) != null) {
                         //Special case if we have a Vector, an ArrayList
                         //or an Array --> need to check if it is not empty
                         if (desc.isMultivalued()) {
                             Object temp = handler.getValue(object);
                             //-- optimize this?
                             if (Array.getLength(temp) == 0) {
                                  temp = null;
                                  continue;
                             }
                             temp = null;
                         }

                        if (found) {
                            String err = null;
                            if (desc.isContainer()) {
                                err = "The group '" + desc.getFieldName();
                                err += "' cannot exist at the same time that ";
                                if (fieldDesc.isContainer()) {
                                    err += "the group '" + fieldDesc.getFieldName();
                                } else {
                                    err += "the element '" + fieldDesc.getXMLName();
                                }
                                err += "' also exists.";
                            } else {
                                 err = "The element '" + desc.getXMLName();
                                 err += "' cannot exist at the same time that ";
                                 err += "element '" + fieldDesc.getXMLName() + "' also exists.";
                            }
                            throw new ValidationException(err);
                        }
                        found = true;
                        fieldDesc = desc;

                        FieldValidator fieldValidator = desc.getValidator();
                        if (fieldValidator != null) {
                            fieldValidator.validate(object, context);
                        }
                    }
                }

                // if all elements are mandatory, print the grammar that the choice 
                // must match.
                if ((!found) && (hasLocalDescs))  {
                    StringBuffer buffer = new StringBuffer(40);
                    boolean existsOptionalElement = false;
                    buffer.append('(');
                    String sep = " | ";
                    for (int i = 0; i < localElements.length; i++) {
                        XMLFieldDescriptor  desc = localElements[i];
                        if (desc == null) {
                            continue;
                        }

                        FieldValidator fieldValidator = desc.getValidator();
                        if (fieldValidator.getMinOccurs() == 0) {
                            existsOptionalElement = true;
                            break;
                        }
                        buffer.append(sep);
                        buffer.append(desc.getXMLName());
                    }
                    buffer.append(')');
                    if (!existsOptionalElement) {
                        String err = "In the choice contained in <" + this.getXMLName()
                                     + ">, at least one of these elements must appear:\n"
                                     + buffer.toString();
                        throw new ValidationException(err);
                    }
                }
                //-- handle attributes, not affected by choice
                for (int i = 0; i < localAttributes.length; i++) {
                    validateField(object, context, localAttributes[i]);
                }
                //-- handle content, not affected by choice
                if (_contentDescriptor != null) {
                    validateField(object, context, _contentDescriptor);
                }
                break;
            //-- Currently SEQUENCE is handled the same as all
            case SEQUENCE:
            //-- ALL
            default:


                //-- handle elements
                for (int i = 0; i < localElements.length; i++) {
                    final XMLFieldDescriptor fieldDescriptor = localElements[i];
                    if (fieldDescriptor == null) {
                        continue;
                    }
                    validateField(object, context, fieldDescriptor);
                }
                //-- handle attributes
                // for (int i = 0; i < _attributes.size(); i++) {
                for (int i = 0; i < localAttributes.length; i++) {
                    validateField(object, context, localAttributes[i]);
                }
                //-- handle content
                if (_contentDescriptor != null) {
                    validateField(object, context, _contentDescriptor);
                }
                break;
        }

    } //-- validate

    /**
     * Validates agiven field of an object, as described by its {@link XMLFieldDescriptor}
     * instance.
     * @param object The parent object, whose field to validate.
     * @param context The current {@link ValidationContext} instance.
     * @param fieldDescriptor The {@link XMLFieldDescriptor} instance describing the 
     *          field to validate.
     * @throws ValidationException If validation did report a problem.
     */
    private void validateField(final Object object, final ValidationContext context,
            final XMLFieldDescriptor fieldDescriptor) throws ValidationException {
        FieldValidator fieldValidator = fieldDescriptor.getValidator();
        if (fieldValidator != null) {
            try {
                fieldValidator.validate(object, context);
            } catch (ValidationException e) {
                if (fieldDescriptor.getNodeType() == NodeType.Attribute 
                        || fieldDescriptor.getNodeType() == NodeType.Element) {
                    addLocationInformation(fieldDescriptor, e);
                }
                throw e;
            }
        }
    }

    /**
     * Adds location information to the {@link ValidationException} instance.
     * @param localElement The {@link XMLFieldDescriptor} instance whose has been responsible for
     * generating the error.
     * @param e The {@link ValidationException} to enrich.
     */
    private void addLocationInformation(final XMLFieldDescriptor localElement,
            final ValidationException e) {
        XPathLocation loc = (XPathLocation) e.getLocation();
        if (loc == null) {
            loc = new XPathLocation();
            e.setLocation(loc);
            if (localElement.getNodeType() == NodeType.Attribute) {
                loc.addAttribute(localElement.getXMLName());
            } else {
                loc.addChild(localElement.getXMLName());
            }
        }
    }

    //-------------------------------------/
    //- Implementation of ClassDescriptor -/
    //-------------------------------------/

    /**
     * Returns the Java class represented by this descriptor.
     *
     * @return The Java class
     */
    public Class<?> getJavaClass() {
        return _class;
    } //-- getJavaClass


    /**
     * Returns a list of fields represented by this descriptor.
     *
     * @return A list of fields
     */
    public FieldDescriptor[] getFields() {
        int size = _attributes.size();
        size += _elements.size();
        if (_contentDescriptor != null) ++size;


        XMLFieldDescriptor[] fields = new XMLFieldDescriptor[size];

        _attributes.toArray(fields);
        _elements.toArray(fields, _attributes.size());

        if (_contentDescriptor != null) {
            fields[size-1] = _contentDescriptor;
        }
        return fields;
    } //-- getFields



    /**
     * Returns the class descriptor of the class extended by this class.
     *
     * @return The extended class descriptor
     */
    public ClassDescriptor getExtends() {
        return _extends;
    } //-- getExtends


    /**
     * Returns the identity field, null if this class has no identity.
     *
     * @return The identity field
     */
    public FieldDescriptor getIdentity() {
        return _identity;
    } //-- getIdentity


    /**
     * Returns the access mode specified for this class.
     *
     * @return The access mode
     */
    public AccessMode getAccessMode() {
        return _accessMode;
    } //-- getAccessMode

    /**
     * @see org.exolab.castor.xml.XMLClassDescriptor#canAccept(java.lang.String,
     *      java.lang.String, java.lang.Object)
     */
    public boolean canAccept(String name, String namespace, Object object) {

        boolean result = false;
        boolean hasValue = false;
        XMLFieldDescriptor[] fields = null;
        int i = 0;
        //1--direct look up for a field
        XMLFieldDescriptor fieldDesc = this.getFieldDescriptor(name, namespace, NodeType.Element);
        if (fieldDesc == null)
           fieldDesc = this.getFieldDescriptor(name, namespace, NodeType.Attribute);

        //if the descriptor is still null, the field can't be in stored in this classDescriptor
        if (fieldDesc == null)
            return false;

        Object tempObject;
        //3--The descriptor is multivalued
        if (fieldDesc.isMultivalued()) {
            //-- check size
            FieldValidator validator = fieldDesc.getValidator();
            if (validator != null) {
                if (validator.getMaxOccurs() < 0) {
                   result = true;
                }
                 else {
                    // count current objects and add 1
                    tempObject = fieldDesc.getHandler().getValue(object);
                    int current = Array.getLength(tempObject);
                    int newTotal = current + 1;
                    result = (newTotal <= validator.getMaxOccurs());
                 }
            }
            else {
                //-- not created by source generator...assume unbounded
                result = true;
            }
        }
        //3-- the fieldDescriptor is a container
        else if (fieldDesc.isContainer()) {
            tempObject = fieldDesc.getHandler().getValue(object);
            //if the object is not yet instantiated, we return true
            if (tempObject == null)
                result = true;
            else
                result = ((XMLClassDescriptor)fieldDesc.getClassDescriptor()).canAccept(name, namespace, tempObject);
        }
        //4-- Check if the value is set or not
        else {
            FieldHandler handler = fieldDesc.getHandler();

            boolean checkPrimitiveValue = true;
            if (handler instanceof AbstractFieldHandler) {
                hasValue = ((AbstractFieldHandler)handler).hasValue( object );
                //-- poor man's check for a generated handler, since
                //-- all generated handlers extend XMLFieldHandler, however
                //-- this doesn't guarantee that that handler is indeed
                //-- a generated handler, it does however mean that
                //-- the handler definately didn't come from the
                //-- MappingLoader.
                //checkPrimitiveValue = (!(handler instanceof XMLFieldHandler));
            }
            else {
                hasValue = (handler.getValue(object) != null);
            }

            //-- patch for primitives, we should check
            //-- for the has-method somehow...but this
            //-- is good enough for now. This may break
            //-- some non-Castor-generated code with
            //-- primitive values that have been set
            //-- with the same as the defaults
            if (hasValue &&
                checkPrimitiveValue &&
                fieldDesc.getFieldType().isPrimitive())
            {
                if (isDefaultPrimitiveValue(handler.getValue( object ))) {
                    hasValue = false;
                }
            }
            //-- end patch
            result = !hasValue;
        }

        //5--if there is no value and the _compositor is CHOICE
        //--we have to check to see if another value has not been set
        if (result && (_compositor == CHOICE)
            && (fieldDesc.getNodeType() == NodeType.Element) ) {
            fields = getElementArray();
            i = 0;
            while (result && i<fields.length) {
                XMLFieldDescriptor desc = fields[i];
                if (desc != fieldDesc && (object!=null) ) {
                    Object tempObj = desc.getHandler().getValue(object);
                    hasValue = (tempObj != null);
                    if (hasValue) {
                        result = false;
                        //special case for array
                        if (tempObj.getClass().isArray()) {
                            result =  Array.getLength(tempObj) == 0;
                        }
                        //special case for collection
                        if (tempObj instanceof Collection) {
                           result = ((Collection) tempObj).isEmpty();
                        }
                    }
                }
                i++;
            }//while
        }//CHOICE
        return result;
    }//--canAccept

    //---------------------/
    //- Protected Methods -/
    //---------------------/


    /**
     * Returns true if the given class should be treated as a primitive
     * type. This method will return true for all Java primitive
     * types, the set of primitive object wrappers, as well
     * as Strings.
     *
     * @return true if the given class should be treated as a primitive
     * type
    **/
    static boolean isPrimitive(Class<?> type) {

        if (type == null) return false;

        //-- java primitive
        if (type.isPrimitive()) return true;

        //-- primtive wrapper classes
        if ((type == Boolean.class) || (type == Character.class))
            return true;

        return (type.getSuperclass() == Number.class);
    } //-- isPrimitive

    /**
     * Checks to see if the given Object is a java primitive
     * (does not check for primitive wrappers) and has a
     * value that is equal to the default value for that
     * primitive. This method will return true if the value
     * is a java primitive with a default value.
     *
     * @return true if the value is a java primitive with
     * a default value
     */
    static boolean isDefaultPrimitiveValue(Object value) {
        if (value == null) return false;

        Class<?> type = value.getClass();
        if (type.isPrimitive()) {
            try {
                return (value.equals(type.newInstance()));
            }
            catch(java.lang.IllegalAccessException iax) {
                //-- Just return false, we should be
                //-- able to instantiate primitive types
            }
            catch(java.lang.InstantiationException ix) {
                //-- Just return false, we should be
                //-- able to instantiate primitive types
            }
        }
        else if (type.getSuperclass() == Number.class) {
            return ((Number)value).intValue() == 0;
        }
        else if (type == Boolean.class) {
            return value.equals(Boolean.FALSE);
        }
        else if (type == Character.class) {
            return ((Character)value).charValue() == '\0';
        }

        return false;
    } //-- isDefaultPrimitiveValue


    /**
     * Sets the Class type being described by this descriptor.
     *
     * @param type the Class type being described
     */
    public void setJavaClass(Class<?> type) {
        this._class = type;
    } //-- setJavaClass

    protected void setExtendsWithoutFlatten(XMLClassDescriptor classDesc) {
        this._extends = classDesc;
    } //-- setExtendsWithoutFlatten

    /**
     * Sets a flag to indicate whether or not this XMLClassDescriptorImpl
     * was created via introspection
     *
     * @param introspected a boolean, when true indicated that this
     * XMLClassDescriptor was created via introspection
     */
    protected void setIntrospected(boolean introspected) {
        this._introspected = introspected;
    } //-- setIntrospected

//    protected String toXMLName(String className) {
//        //-- create default XML name
//        String name = className;
//        int idx = name.lastIndexOf('.');
//        if (idx >= 0) name = name.substring(idx+1);
//        return _naming.toXMLName(name);
//    }

    //-------------------/
    //- Private Methods -/
    //-------------------/

    /**
     * Adds the given XMLFieldDescriptor to the list of descriptors. The
     * descriptor will be added to the appropriate list by calling
     * XMLFieldDescriptor#getNodeType() to determine it's type.
     *
     * @param descriptor the XMLFieldDescriptor to add
     */
    private void addFieldDescriptor(XMLFieldDescriptor descriptor, boolean relink) {

        if (descriptor == null) return;

        boolean added = false;

        NodeType nodeType = descriptor.getNodeType();
        switch(nodeType.getType()) {
            case NodeType.NAMESPACE:
            case NodeType.ATTRIBUTE:
                added = _attributes.add(descriptor);
                if (added) {
                    _attArray = null;
                }
                break;
            case NodeType.TEXT:
                _contentDescriptor = descriptor;
                added = true;
                break;
            default:
                added = _elements.add(descriptor);
                if (added) {
                    _elemArray = null;
                    if (descriptor.isContainer()) ++_containerCount;
                }
                break;
        }

        if ((added) && (relink)) {
            descriptor.setContainingClassDescriptor( this );
        }

    } //-- addFieldDescriptor


    private XMLFieldDescriptor[] getAttributeArray() {
        //-- create local reference to prevent possible
        //-- null pointer (_attArray could be re-set to null)
        //-- in multi-threaded environment
        XMLFieldDescriptor[] descriptors = _attArray;
        if (descriptors == null) {
            descriptors = _attributes.toArray();
            _attArray = descriptors;
        }
        return descriptors;
    }

    private XMLFieldDescriptor[] getElementArray() {
        //-- create local reference to prevent possible
        //-- null pointer (_elemArray could be re-set to null)
        //-- in multi-threaded environment
        XMLFieldDescriptor[] descriptors = _elemArray;
        if (descriptors == null) {
            descriptors = _elements.toArray();
            _elemArray = descriptors;
        }
        return descriptors;
    }
    
    /**
     * Adds a XMLFieldDescriptor instance to the internally maintained
     * list of sequence elements.
     * @param element An {@link XMLFieldDescriptor} instance for an element definition.
     */
    protected void addSequenceElement(XMLFieldDescriptor element) {
        _sequenceOfElements.add(element);
    }
    
    public List<String> getSubstitutes() {
        return _substitutes;
    }

    public void setSubstitutes(List<String> substitutes) {
        _substitutes = substitutes;
    }
         
    public boolean isChoice() {
        return this._compositor == CHOICE;
    }
    
    /**
     * @see org.exolab.castor.builder.info.nature.PropertyHolder#
     *      getProperty(java.lang.String)
     * @param name
     *            of the property
     * @return value of the property
     */
    public Object getProperty(final String name) {
        return _properties.get(name);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.PropertyHolder#
     *      setProperty(java.lang.String, java.lang.Object)
     * @param name
     *            of the property
     * @param value
     *            of the property
     */
    public void setProperty(final String name, final Object value) {
        _properties.put(name, value);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.NatureExtendable#
     *      addNature(java.lang.String)
     * @param nature
     *            ID of the Nature
     */
    public void addNature(final String nature) {
        _natures.add(nature);
    }

    /**
     * @see org.exolab.castor.builder.info.nature.NatureExtendable#
     *      hasNature(java.lang.String)
     * @param nature
     *            ID of the Nature
     * @return true if the Nature ID was added.
     */
    public boolean hasNature(final String nature) {
        return _natures.contains(nature);
    }
    
}
