/**
 * 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;


import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.mapping.BindingType;
import org.exolab.castor.mapping.AbstractFieldHandler;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.CollectionHandler;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.MapItem;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.TypeConvertor;
import org.exolab.castor.mapping.loader.FieldDescriptorImpl;
import org.exolab.castor.mapping.loader.AbstractMappingLoader;
import org.exolab.castor.mapping.loader.CollectionHandlers;
import org.exolab.castor.mapping.loader.FieldHandlerImpl;
import org.exolab.castor.mapping.loader.TypeInfo;
import org.exolab.castor.mapping.loader.Types;
import org.exolab.castor.mapping.xml.BindXml;
import org.exolab.castor.mapping.xml.ClassMapping;
import org.exolab.castor.mapping.xml.FieldMapping;
import org.exolab.castor.mapping.xml.MapTo;
import org.exolab.castor.mapping.xml.MappingRoot;
import org.exolab.castor.mapping.xml.Property;
import org.exolab.castor.mapping.xml.types.BindXmlAutoNamingType;
import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType;
import org.exolab.castor.xml.handlers.ContainerFieldHandler;
import org.exolab.castor.xml.handlers.ToStringFieldHandler;
import org.exolab.castor.xml.util.ContainerElement;
import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
import org.exolab.castor.xml.util.XMLContainerElementFieldDescriptor;
import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
import org.exolab.castor.xml.validators.IdRefValidator;
import org.exolab.castor.xml.validators.NameValidator;

/**
 * An XML implementation of mapping helper. Creates XML class descriptors from the mapping file.
 *
 * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
 * @author <a href="arkin@intalio.com">Assaf Arkin</a>
 * @version $Revision$ $Date: 2006-02-23 01:37:50 -0700 (Thu, 23 Feb 2006) $
 */
public final class XMLMappingLoader extends AbstractMappingLoader {

  /**
   * {@link Log} instance to be used.
   */
  private static final Log LOG = LogFactory.getLog(XMLMappingLoader.class);

  // -----------------------------------------------------------------------------------

  /**
   * The default xml prefix used on certain attributes such as xml:lang, xml:base, etc.
   */
  private static final String XML_PREFIX = "xml:";

  /** Empty array of class types used for reflection. */
  private static final Class[] EMPTY_ARGS = new Class[0];

  /** The NCName Schema type. */
  private static final String NCNAME = "NCName";

  /** The string argument for the valueOf method, used for introspection. */
  private static final Class[] STRING_ARG = {String.class};

  /**
   * Factory method name for type-safe enumerations. This is primarily for allowing users to map
   * classes that were created by Castor's SourceGenerator.
   */
  private static final String VALUE_OF = "valueOf";

  /**
   * Creates a new XMLMappingLoader. Joachim 2007-08-19: called via ClassLoader from
   * XMLMappingLoaderFactory.getMappingLoader() must not be modified!!!
   * 
   * @param loader the class loader to use
   */
  public XMLMappingLoader(final ClassLoader loader) {
    super(loader);
  }

  /**
   * {@inheritDoc}
   */
  public BindingType getBindingType() {
    return BindingType.XML;
  }

  /**
   * {@inheritDoc}
   */
  public void loadMapping(final MappingRoot mapping, final Object param) throws MappingException {
    if (loadMapping()) {
      createFieldHandlers(mapping);
      createClassDescriptors(mapping);
    }
  }

  // -----------------------------------------------------------------------------------

  /**
   * To create the class descriptor for the given class mapping. Throws IllegalStateException if the
   * class has no valid internal context.
   * 
   * @param classMapping the class mapping information to process
   * @return the {@link ClassDescriptor} created for the class mapping
   * @throws MappingException ...
   */
  protected ClassDescriptor createClassDescriptor(final ClassMapping classMapping)
      throws MappingException {
    // execution makes no sense without a context or without a resolver...
    if ((getInternalContext() == null)
        || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
      String message = "Internal context or class descriptor resolver within are not valid";
      LOG.warn(message);
      throw new IllegalStateException(message);
    }
    // Create the class descriptor.
    XMLClassDescriptorAdapter xmlClassDesc = new XMLClassDescriptorAdapter();

    // Introspection and package level stuff needs to be disabled !!
    getInternalContext().getXMLClassDescriptorResolver().setUseIntrospection(false);
    getInternalContext().getXMLClassDescriptorResolver().setLoadPackageMappings(false);

    try {
      if (classMapping.getAutoComplete()) {
        if ((classMapping.getMapTo() == null)
            && ((classMapping.getClassChoice() == null)
                || (classMapping.getClassChoice().getFieldMappingCount() == 0))
            && (classMapping.getIdentityCount() == 0)) {
          // If we make it here we simply try to load a compiled mapping
          try {
            ClassDescriptor clsDesc = getInternalContext().getXMLClassDescriptorResolver()
                .resolve(classMapping.getName());
            if (clsDesc != null) {
              return clsDesc;
            }
          } catch (ResolverException e) {
            if (LOG.isDebugEnabled()) {
              LOG.debug("Ignoring exception: " + e + " at resolving: " + classMapping.getName());
            }
          }
        }
      }

      // Obtain the Java class.
      Class javaClass = resolveType(classMapping.getName());
      if (classMapping.getVerifyConstructable()) {
        if (!Types.isConstructable(javaClass, true)) {
          throw new MappingException("mapping.classNotConstructable", javaClass.getName());
        }
      }
      xmlClassDesc.setJavaClass(javaClass);

      // Obtain XML name.
      String xmlName;
      MapTo mapTo = classMapping.getMapTo();
      if ((mapTo != null) && (mapTo.getXml() != null)) {
        xmlName = mapTo.getXml();
      } else {
        String clsName = getInternalContext().getJavaNaming().getClassName(javaClass);
        xmlName = getInternalContext().getXMLNaming().toXMLName(clsName);
      }
      xmlClassDesc.setXMLName(xmlName);

      // If this class extends another class, we need to obtain the extended
      // class and make sure this class indeed extends it.
      ClassDescriptor extDesc = getExtended(classMapping, javaClass);
      xmlClassDesc.setExtends((XMLClassDescriptor) extDesc);

      // Create all field descriptors.
      FieldDescriptorImpl[] allFields = createFieldDescriptors(classMapping, javaClass);

      // Make sure there are no two fields with the same name.
      checkFieldNameDuplicates(allFields, javaClass);

      // Identify identity and normal fields. Note that order must be preserved.
      List<FieldDescriptorImpl> fieldList = new ArrayList<>(allFields.length);
      List<FieldDescriptor> idList = new ArrayList<>();
      if (extDesc == null) {
        // Sort fields into 2 lists based on identity definition of field.
        for (int i = 0; i < allFields.length; i++) {
          if (!allFields[i].isIdentity()) {
            fieldList.add(allFields[i]);
          } else {
            idList.add(allFields[i]);
          }
        }

        if (idList.isEmpty()) {
          // Found no identities based on identity definition of field.
          // Try to find identities based on identity definition on class.
          String[] idNames = classMapping.getIdentity();

          for (int i = 0; i < idNames.length; i++) {
            FieldDescriptor identity = findIdentityByName(fieldList, idNames[i], javaClass);
            if (identity != null) {
              idList.add(identity);
            } else {
              throw new MappingException("mapping.identityMissing", idNames[i],
                  javaClass.getName());
            }
          }
        }
      } else {
        // Add all fields of extending class to field list.
        for (int i = 0; i < allFields.length; i++) {
          fieldList.add(allFields[i]);
        }

        // Add identity of extended class to identity list.
        if (extDesc.getIdentity() != null) {
          idList.add(extDesc.getIdentity());
        }

        // Search redefined identities in extending class.
        for (int i = 0; i < idList.size(); i++) {
          String idname = idList.get(i).getFieldName();
          FieldDescriptor identity = findIdentityByName(fieldList, idname, javaClass);
          if (identity != null) {
            idList.set(i, identity);
          }
        }
      }

      FieldDescriptor xmlId = null;
      if (!idList.isEmpty()) {
        xmlId = (FieldDescriptor) idList.get(0);
      }

      if (xmlId != null) {
        xmlClassDesc.setIdentity((XMLFieldDescriptorImpl) xmlId);
      }
      for (FieldDescriptor fieldDesc : fieldList) {
        if (fieldDesc != null) {
          xmlClassDesc.addFieldDescriptor((XMLFieldDescriptorImpl) fieldDesc);
        }
      }

      if (classMapping.getAutoComplete()) {

        XMLClassDescriptor referenceDesc = null;

        Class type = xmlClassDesc.getJavaClass();

        // -- check compiled descriptors
        if ((getInternalContext() == null)
            || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
          String message = "Internal context or class descriptor resolver within are not valid";
          LOG.warn(message);
          throw new IllegalStateException(message);
        }
        try {
          referenceDesc = (XMLClassDescriptor) getInternalContext().getXMLClassDescriptorResolver()
              .resolve(type);
        } catch (ResolverException rx) {
          throw new MappingException(rx);
        }

        if (referenceDesc == null) {
          Introspector introspector = getInternalContext().getIntrospector();
          try {
            referenceDesc = introspector.generateClassDescriptor(type);
            if (classMapping.getExtends() != null) {
              // -- clear parent from introspected descriptor since
              // -- a mapping was provided in the mapping file
              ((XMLClassDescriptorImpl) referenceDesc).setExtends(null);
            }
          } catch (MarshalException mx) {
            String error =
                "unable to introspect class '" + type.getName() + "' for auto-complete: ";
            throw new MappingException(error + mx.getMessage());
          }
        }

        // -- check for identity
        String identity = "";
        if (classMapping.getIdentityCount() > 0) {
          identity = classMapping.getIdentity(0);
        }


        FieldDescriptor[] xmlFields2 = xmlClassDesc.getFields();

        // Attributes
        XMLFieldDescriptor[] introFields = referenceDesc.getAttributeDescriptors();
        for (int i = 0; i < introFields.length; ++i) {
          if (!isMatchFieldName(xmlFields2, introFields[i].getFieldName())) {
            // If there is no field with this name, we can add it
            if (introFields[i].getFieldName().equals(identity)) {
              xmlClassDesc.setIdentity(introFields[i]);
            } else {
              xmlClassDesc.addFieldDescriptor(introFields[i]);
            }
          }
        }

        // Elements
        introFields = referenceDesc.getElementDescriptors();
        for (int i = 0; i < introFields.length; ++i) {
          if (!isMatchFieldName(xmlFields2, introFields[i].getFieldName())) {
            // If there is no field with this name, we can add it
            if (introFields[i].getFieldName().equals(identity)) {
              xmlClassDesc.setIdentity(introFields[i]);
            } else {
              xmlClassDesc.addFieldDescriptor(introFields[i]);
            }
          }
        }

        // Content
        XMLFieldDescriptor field = referenceDesc.getContentDescriptor();
        if (field != null) {
          if (!isMatchFieldName(xmlFields2, field.getFieldName())) {
            // If there is no field with this name, we can add
            xmlClassDesc.addFieldDescriptor(field);
          }
        }
      }

      // Copy ns-uri + ns-prefix + element-definition
      if (mapTo != null) {
        xmlClassDesc.setNameSpacePrefix(mapTo.getNsPrefix());
        xmlClassDesc.setNameSpaceURI(mapTo.getNsUri());
        xmlClassDesc.setElementDefinition(mapTo.getElementDefinition());
      }
    } finally {
      getInternalContext().getXMLClassDescriptorResolver().setUseIntrospection(true);
      getInternalContext().getXMLClassDescriptorResolver().setLoadPackageMappings(true);
    }

    return xmlClassDesc;
  }

  protected final FieldDescriptor findIdentityByName(final List fldList, final String idName,
      final Class javaClass) {
    for (int i = 0; i < fldList.size(); i++) {
      FieldDescriptor field = (FieldDescriptor) fldList.get(i);
      if (idName.equals(field.getFieldName())) {
        fldList.remove(i);
        return field;
      }
    }
    return null;
  }

  protected final void resolveRelations(ClassDescriptor clsDesc) {
    FieldDescriptor[] fields;

    fields = clsDesc.getFields();
    for (int i = 0; i < fields.length; ++i) {
      if (fields[i].getClassDescriptor() != null)
        continue;
      ClassDescriptor relDesc;

      Class fieldType = fields[i].getFieldType();
      if (fieldType != null) {
        relDesc = getDescriptor(fieldType.getName());
        if (relDesc != null && relDesc instanceof XMLClassDescriptor
            && fields[i] instanceof XMLFieldDescriptorImpl) {
          ((XMLFieldDescriptorImpl) fields[i]).setClassDescriptor(relDesc);
        }
      }
    }
    if (clsDesc instanceof XMLClassDescriptorImpl)
      ((XMLClassDescriptorImpl) clsDesc).sortDescriptors();
  }

  // -----------------------------------------------------------------------------------

  /**
   * Match if a field named <code>fieldName</code> is in fields
   */
  private boolean isMatchFieldName(FieldDescriptor[] fields, String fieldName) {
    for (int i = 0; i < fields.length; ++i)
      if (fields[i].getFieldName().equals(fieldName))
        return true;

    return false;
  } // -- method: isMatchFieldName


  protected FieldDescriptorImpl createFieldDesc(Class javaClass, FieldMapping fieldMap)
      throws MappingException {

    FieldDescriptor fieldDesc;
    FieldMappingCollectionType colType = fieldMap.getCollection();
    String xmlName = null;
    NodeType nodeType = null;
    String match = null;
    XMLFieldDescriptorImpl xmlDesc;
    boolean isReference = false;
    boolean isXMLTransient = false;

    // -- handle special case for HashMap/Hashtable
    if ((fieldMap.getType() == null) && (colType != null)) {
      if ((colType == FieldMappingCollectionType.HASHTABLE)
          || (colType == FieldMappingCollectionType.MAP)
          || (colType == FieldMappingCollectionType.SORTEDMAP)) {
        fieldMap.setType(MapItem.class.getName());
      }
    }

    // Create an XML field descriptor
    fieldDesc = super.createFieldDesc(javaClass, fieldMap);

    BindXml xml = fieldMap.getBindXml();

    boolean deriveNameByClass = false;

    if (xml != null) {
      // -- xml name
      xmlName = xml.getName();

      // -- node type
      if (xml.getNode() != null)
        nodeType = NodeType.getNodeType(xml.getNode().toString());

      // -- matches
      match = xml.getMatches();

      // -- reference
      isReference = xml.getReference();

      // -- XML transient
      isXMLTransient = xml.getTransient();

      // -- autonaming
      BindXmlAutoNamingType autoName = xml.getAutoNaming();
      if (autoName != null) {
        deriveNameByClass = (autoName == BindXmlAutoNamingType.DERIVEBYCLASS);
      }

    }

    // -- transient
    // -- XXXX -> if it's transient we probably shouldn't do all
    // -- XXXX -> the unecessary work
    isXMLTransient = isXMLTransient || fieldDesc.isTransient();

    // --

    // -- handle QName for xmlName
    String namespace = null;
    if ((xmlName != null) && (xmlName.length() > 0)) {
      if (xmlName.charAt(0) == '{') {
        int idx = xmlName.indexOf('}');
        if (idx < 0) {
          throw new MappingException("Invalid QName: " + xmlName);
        }
        namespace = xmlName.substring(1, idx);
        xmlName = xmlName.substring(idx + 1);
      } else if (xmlName.startsWith(XML_PREFIX)) {
        namespace = Namespaces.XML_NAMESPACE;
        xmlName = xmlName.substring(4);
      }
    }

    if (nodeType == null) {
      if (isPrimitive(javaClass))
        nodeType = getInternalContext().getPrimitiveNodeType();
      else
        nodeType = NodeType.Element;
    }

    // -- Create XML name if necessary. Note if name is to be derived
    // -- by class..we just make sure we set the name to null...
    // -- the Marshaller does this during runtime. This allows
    // -- Collections to be handled properly.
    if ((!deriveNameByClass) && ((xmlName == null) && (match == null))) {
      xmlName = getInternalContext().getXMLNaming().toXMLName(fieldDesc.getFieldName());
      match = xmlName + ' ' + fieldDesc.getFieldName();
    }

    xmlDesc = new XMLFieldDescriptorImpl(fieldDesc, xmlName, nodeType,
        getInternalContext().getPrimitiveNodeType());

    if (xmlDesc.getHandler() != null && xmlDesc.getHandler() instanceof AbstractFieldHandler) {
      AbstractFieldHandler handler = (AbstractFieldHandler) xmlDesc.getHandler();
      handler.setFieldDescriptor(xmlDesc);
    }

    // -- transient?
    xmlDesc.setTransient(isXMLTransient);

    // --set a default fieldValidator
    xmlDesc.setValidator(new FieldValidator());

    // -- enable use parent namespace if explicit one doesn't exist
    xmlDesc.setUseParentsNamespace(true);

    // -- If deriveNameByClass we need to reset the name to
    // -- null because XMLFieldDescriptorImpl tries to be smart
    // -- and automatically creates the name.
    if (deriveNameByClass) {
      xmlDesc.setXMLName(null);
    }

    // -- namespace
    if (namespace != null) {
      xmlDesc.setNameSpaceURI(namespace);
    }

    // -- matches
    if (match != null) {
      xmlDesc.setMatches(match);
      // -- special fix for xml-name since XMLFieldDescriptorImpl
      // -- will create a default name based off the field name
      if (xmlName == null)
        xmlDesc.setXMLName(null);
    }

    // -- reference
    xmlDesc.setReference(isReference);
    if (isReference) {
      if (colType == null) {
        FieldValidator fieldValidator = new FieldValidator();
        fieldValidator.setValidator(new IdRefValidator());
        xmlDesc.setValidator(fieldValidator);
      } else {
        // TODO handle other cases
      }
    }

    xmlDesc.setContainer(fieldMap.getContainer());

    xmlDesc.setNillable(fieldMap.isNillable());

    if (xml != null) {

      // -- has class descriptor for type specified
      if (xml.getClassMapping() != null) {
        ClassDescriptor cd = createClassDescriptor(xml.getClassMapping());
        xmlDesc.setClassDescriptor(cd);
      }

      // -- has location path?
      if (xml.getLocation() != null) {
        xmlDesc.setLocationPath(xml.getLocation());
      }
      // is the value type needs specific handling
      // such as QName or NCName support?
      String xmlType = xml.getType();
      xmlDesc.setSchemaType(xmlType);
      xmlDesc.setQNamePrefix(xml.getQNamePrefix());
      TypeValidator validator = null;
      if (NCNAME.equals(xmlType)) {
        validator = new NameValidator(XMLConstants.NAME_TYPE_NCNAME);
        xmlDesc.setValidator(new FieldValidator(validator));
      }

      // -- special properties?
      Property[] props = xml.getProperty();
      if ((props != null) && (props.length > 0)) {
        for (int pIdx = 0; pIdx < props.length; pIdx++) {
          Property prop = props[pIdx];
          xmlDesc.setXMLProperty(prop.getName(), prop.getValue());
        }
      }
    }

    // -- Get collection type
    if (colType == null) {
      // -- just in case user forgot to use collection="..."
      // -- in the mapping file
      Class type = fieldDesc.getFieldType();
      if (type != null && CollectionHandlers.hasHandler(type)) {
        String typeName = CollectionHandlers.getCollectionName(type);
        colType = FieldMappingCollectionType.fromValue(typeName);
      }
    }

    // -- isMapped item
    if (colType != null) {
      if ((colType == FieldMappingCollectionType.HASHTABLE)
          || (colType == FieldMappingCollectionType.MAP)
          || (colType == FieldMappingCollectionType.SORTEDMAP)) {
        // -- Make sure user is not using an addMethod
        // -- before setting the mapped field to true.
        String methodName = fieldMap.getSetMethod();
        if (methodName != null) {
          if (!methodName.startsWith("add")) {
            xmlDesc.setMapped(true);
          }
        } else
          xmlDesc.setMapped(true);
      }


      // -- special NodeType.Namespace handling
      // -- prevent FieldHandlerImpl from using CollectionHandler
      // -- during calls to #getValue
      if ((nodeType == NodeType.Namespace) || (xmlDesc.isMapped())) {
        Object handler = xmlDesc.getHandler();
        if (handler instanceof FieldHandlerImpl) {
          FieldHandlerImpl handlerImpl = (FieldHandlerImpl) handler;
          handlerImpl.setConvertFrom(new IdentityConvertor());
        }
      }
      // -- wrap collection in element?
      if (nodeType == NodeType.Element) {
        if (fieldMap.hasContainer() && (!fieldMap.getContainer())) {
          xmlDesc = wrapCollection(xmlDesc);
        }
      }
    }

    // -- is Type-Safe Enumeration?
    // -- This is not very clean, we should have a way
    // -- to specify something is a type-safe enumeration
    // -- without having to guess.
    else if ((!isReference) && (!isXMLTransient)) {
      Class fieldType = xmlDesc.getFieldType();
      if (!isPrimitive(fieldType)) {
        // -- make sure no default constructor
        Constructor cons = null;
        try {
          cons = fieldType.getConstructor(EMPTY_ARGS);
          if (!Modifier.isPublic(cons.getModifiers())) {
            cons = null;
          }
        } catch (NoSuchMethodException nsmx) {
          // -- Do nothing
        }
        try {
          if (cons == null) {
            // -- make sure a valueOf factory method
            // -- exists and no user specified handler exists
            Method method = fieldType.getMethod(VALUE_OF, STRING_ARG);
            Class returnType = method.getReturnType();
            if ((returnType != null) && fieldType.isAssignableFrom(returnType)) {
              if (fieldMap.getHandler() == null) {
                // -- Use EnumFieldHandler
                // -- mapping loader now supports a basic EnumFieldHandler
                // -- for xml we simply need to make sure the toString()
                // -- method is called during getValue()
                // FieldHandler handler = xmlDesc.getHandler();
                // handler = new EnumFieldHandler(fieldType, handler);

                FieldHandler handler = new ToStringFieldHandler(fieldType, xmlDesc.getHandler());

                xmlDesc.setHandler(handler);
                xmlDesc.setImmutable(true);
              }
            }
          }
        } catch (NoSuchMethodException nsmx) {
          // -- Do nothing
        }
      }
    }

    // -- constructor argument?
    String setter = fieldMap.getSetMethod();
    if (setter != null && setter.startsWith("%")) {
      String parameterNumberAsString = setter.substring(1).trim();
      int index = Integer.parseInt(parameterNumberAsString);
      if (index < 1) {
        throw new MappingException("mapper.invalidParameterIndex", parameterNumberAsString);
      }
      // -- adjust index to base zero
      xmlDesc.setConstructorArgumentIndex(--index);
    }

    return xmlDesc;
  }

  /**
   * Sets whether or not to look for and load package specific mapping files (".castor.xml" files).
   *
   * @param loadPackageMappings a boolean that enables or disables the loading of package specific
   *        mapping files
   */
  public void setLoadPackageMappings(final boolean loadPackageMappings) {
    if ((getInternalContext() == null)
        || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
      String message = "Internal context or class descriptor resolver within are not valid";
      LOG.warn(message);
      throw new IllegalStateException(message);
    }
    getInternalContext().getXMLClassDescriptorResolver()
        .setLoadPackageMappings(loadPackageMappings);
  } // -- setLoadPackageMappings


  protected TypeInfo getTypeInfo(Class fieldType, CollectionHandler colHandler,
      FieldMapping fieldMap) throws MappingException {
    return new TypeInfo(fieldType, null, null, fieldMap.getRequired(), null, colHandler, false);
  }

  /**
   * This method allows a collection to be treated as a first class object (and not a container) so
   * that it has an element representation in the marshalled XML.
   */
  private XMLFieldDescriptorImpl wrapCollection(XMLFieldDescriptorImpl fieldDesc)
      throws MappingException {
    // -- If we have a field 'c' that is a collection and
    // -- we want to wrap that field in an element <e>, we
    // -- need to create a field descriptor for
    // -- an object that represents the element <e> and
    // -- acts as a go-between from the parent of 'c'
    // -- denoted as P(c) and 'c' itself
    //
    // object model: P(c) -> c
    // xml : <p><e><c></e><p>

    // -- Make new class descriptor for the field that
    // -- will represent the container element <e>
    Class type = ContainerElement.class;
    XMLClassDescriptorImpl classDesc = new XMLClassDescriptorImpl(type);
    // -- make copy of fieldDesc and add it to our new class descriptor
    XMLFieldDescriptorImpl newFieldDesc =
        new XMLFieldDescriptorImpl(fieldDesc, fieldDesc.getXMLName(), fieldDesc.getNodeType(),
            getInternalContext().getPrimitiveNodeType());
    // -- nullify xmlName so that auto-naming will be enabled,
    // -- we can't do this in the constructor because
    // -- XMLFieldDescriptorImpl will create a default one.
    newFieldDesc.setXMLName(null);
    newFieldDesc.setMatches("*");

    // -- add the field descriptor to our new class descriptor
    classDesc.addFieldDescriptor(newFieldDesc);
    // -- reassociate the orignal class descriptor (for 'c')
    // of fieldDesc with our new classDesc
    fieldDesc.setClassDescriptor(classDesc);

    // -- wrap the field handler in a special container field
    // -- handler that will actually do the delgation work
    FieldHandler handler = new ContainerFieldHandler(fieldDesc.getHandler());
    newFieldDesc.setHandler(handler);
    fieldDesc.setHandler(handler);

    // -- Change fieldType of original field descriptor and
    // -- return new descriptor
    return new XMLContainerElementFieldDescriptor(fieldDesc,
        getInternalContext().getPrimitiveNodeType());
  } // -- createWrapperDescriptor

  /**
   * A special TypeConvertor that simply returns the object given. This is used for preventing the
   * FieldHandlerImpl from using a CollectionHandler when getValue is called.
   **/
  class IdentityConvertor implements TypeConvertor {
    public Object convert(final Object object) {
      return object;
    }
  } // -- class: IdentityConvertor
} // -- class: XMLMappingLoader


