/**
 * 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: XMLMappingLoader.java 8152 2009-03-30 08:40:08Z wguttmn $
 */
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: 8152 $ $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()) {
                            String message =
                                new StringBuffer().append("Ignoring exception: ").append(e)
                                .append(" at resolving: ").append(classMapping.getName()).toString();
                            LOG.debug(message);
                        }
                    }
                }
            }
            
            // 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 fieldList = new ArrayList(allFields.length);
            List 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.size() == 0) {
                    // Found no identities based on identity definition of field.
                    // Try to find identities based on identity definition on class.
                    String[] idNames = classMapping.getIdentity();
                    
                    FieldDescriptor identity;
                    for (int i = 0; i < idNames.length; i++) {
                        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.
                FieldDescriptor identity;
                for (int i = 0; i < idList.size(); i++) {
                    String idname = ((FieldDescriptor) idList.get(i)).getFieldName();
                    identity = findIdentityByName(fieldList, idname, javaClass);
                    if (identity != null) { idList.set(i, identity); }
                }
            }
            
            FieldDescriptor xmlId = null;
            if (idList.size() != 0) { xmlId = (FieldDescriptor) idList.get(0); }
            
            if (xmlId != null) { xmlClassDesc.setIdentity((XMLFieldDescriptorImpl) xmlId); }
            for (int i = 0; i < fieldList.size(); i++) {
                FieldDescriptor fieldDesc = (FieldDescriptor) fieldList.get(i);
                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.valueOf(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



