/*
 * 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$
 */
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$ $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 final List<XMLFieldDescriptor> _sequenceOfElements = new ArrayList<XMLFieldDescriptor>();

  private List<String> _substitutes = new LinkedList<String>();

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

  /** Map holding the available natures. */
  private final 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.isEmpty()) {

      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)) {
          StringBuilder buffer = new StringBuilder(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).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;
            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);
  }

}
