/*
 * 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-2003 (C) Intalio, Inc. All Rights Reserved.
 *
 * Portions of this file developed by Keith Visco after Jan 19 2005 are
 * Copyright (C) 2005 Keith Visco. All Rights Reserverd.
 *
 * $Id: AbstractMappingLoader.java 8813 2011-02-25 07:01:50Z wguttmn $
 */
package org.exolab.castor.mapping.loader;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;


import org.castor.xml.InternalContext;
import org.castor.xml.AbstractInternalContext;
import org.castor.core.util.Messages;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.ClonableFieldHandler;
import org.exolab.castor.mapping.CollectionHandler;
import org.exolab.castor.mapping.ConfigurableFieldHandler;
import org.exolab.castor.mapping.ExtendedFieldHandler;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.GeneralizedFieldHandler;
import org.exolab.castor.mapping.MapItem;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.ValidityException;
import org.exolab.castor.mapping.handlers.EnumFieldHandler;
import org.exolab.castor.mapping.handlers.TransientFieldHandler;
import org.exolab.castor.mapping.xml.ClassChoice;
import org.exolab.castor.mapping.xml.FieldHandlerDef;
import org.exolab.castor.mapping.xml.MappingRoot;
import org.exolab.castor.mapping.xml.ClassMapping;
import org.exolab.castor.mapping.xml.FieldMapping;
import org.exolab.castor.mapping.xml.Param;

/**
 * Assists in the construction of descriptors. Can be used as a mapping
 * resolver to the engine. Engines will implement their own mapping
 * scheme typically by extending this class.
 *
 * @author <a href="arkin@intalio.com">Assaf Arkin</a>
 * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
 * @version $Revision: 8813 $ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $
 */
public abstract class AbstractMappingLoader extends AbstractMappingLoader2 {

    /** The prefix for the "add" method. */
    private static final String ADD_METHOD_PREFIX = "add";

    /** The prefix for an enumeration method. */
    private static final String ENUM_METHOD_PREFIX = "enum";

    /** The prefix for an enumeration method. */
    private static final String ITER_METHOD_PREFIX = "iterate";

    /** The standard prefix for the getter method. */
    private static final String GET_METHOD_PREFIX = "get";

    /** The prefix for the "is" method for booleans. */
    private static final String IS_METHOD_PREFIX = "is";

    /** The standard prefix for the setter method. */
    private static final String SET_METHOD_PREFIX = "set";

    /** The prefix for the "create" method. */
    private static final String CREATE_METHOD_PREFIX = "create";

    /** The prefix for the "has" method. */
    private static final String HAS_METHOD_PREFIX = "has";

    /** The prefix for the "delete" method. */
    private static final String DELETE_METHOD_PREFIX = "delete";

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

    /** The string argument for the valueOf method, used for introspection when searching for
     *  type-safe enumeration style classes. */
    protected static final Class<?>[] STRING_ARG = {String.class};

    /** Factory method name for type-safe enumerations. */
    protected static final String VALUE_OF = "valueOf";

    /** Method name to get string value of a type-safe enumerations. */
    protected static final String NAME = "name";

    /**
     * The {@link AbstractInternalContext} is the centerpiece providing runtime configuration
     * and state information.
     */
    private InternalContext _internalContext;

    /** Map of field handlers associated by their name. */
    private final Map<String, FieldHandler> _fieldHandlers = new HashMap<String, FieldHandler>();

    /**
     * Constructs a new mapping helper. This constructor is used by a derived class.
     *
     * @param loader The class loader to use, null for the default
     */
    protected AbstractMappingLoader(final ClassLoader loader) {
        super(loader);
    }

    /**
     * {@inheritDoc}
     */
    public final String getSourceType() {
        return "CastorXmlMapping";
    }

    /**
     * Loads the mapping from the specified mapping object if not loaded previously.
     *
     * @param mapping The mapping information.
     * @param param Arbitrary parameter that can be used by subclasses.
     * @throws MappingException The mapping file is invalid.
     */
    public abstract void loadMapping(final MappingRoot mapping, final Object param)
    throws MappingException;
    
    /**
     * Load field handler definitions, check for duplicate definitions and 
     * instantiate the appropriate FieldHandler implementations.
     * 
     * @param mapping Mapping to load field handler definitions from.
     * @throws MappingException If mapping contains more then one field handler
     *        definition with same name.
     */
    protected void createFieldHandlers(final MappingRoot mapping)
    throws MappingException {
        Enumeration<? extends FieldHandlerDef> enumeration = mapping.enumerateFieldHandlerDef();
        while (enumeration.hasMoreElements()) {
            FieldHandlerDef def = enumeration.nextElement();
            
            String name = def.getName();
            
            if (_fieldHandlers.containsKey(name)) {
                throw new MappingException(Messages.format("mapping.dupFieldHandler", name));
            }
            
            
            Class<?> clazz = resolveType(def.getClazz());
            FieldHandler fieldHandler = null;
            try {
                if (!FieldHandler.class.isAssignableFrom(clazz)) {
                    throw new MappingException(Messages.format("mapping.classNotFieldHandler", 
                            name, def.getClazz()));
                }
                fieldHandler = (FieldHandler) clazz.newInstance();
                _fieldHandlers.put(name, fieldHandler);
            } catch (InstantiationException e) {
                throw new MappingException(e);
            } catch (IllegalAccessException e) {
                throw new MappingException(e);
            }
            
            // Add configuration data, if there is any
            configureFieldHandler(def, fieldHandler);
            
         }
    }

    /*
     * Checks if the field handler is configurable, and adds the configuration
     * data to the field handler if this is the case.
     */
    private void configureFieldHandler(final FieldHandlerDef def, final FieldHandler fieldHandler) 
    throws MappingException {
        
        // Gather the configuration data (parameters).
        Properties params = new Properties();            
        Enumeration<? extends Param> enumerateParam = def.enumerateParam();
        while (enumerateParam.hasMoreElements()) {
            Param par = enumerateParam.nextElement();
            params.put(par.getName(), par.getValue());
        }
        
        // If there is configuration data, make sure that the field handler class
        // supports it.
        if (params.size() > 0) {
            if (!ConfigurableFieldHandler.class.isAssignableFrom(fieldHandler.getClass())) {
                throw new MappingException(Messages.format("mapping.classNotConfigurableFieldHandler", 
                        def.getName(), def.getClazz()));
            }
            
            // Pass the configuration data to the field handler.
            try {
                ((ConfigurableFieldHandler)fieldHandler).setConfiguration(params);
            } catch (ValidityException e) {
                throw new MappingException(Messages.format("mapping.invalidFieldHandlerConfig",
                        def.getName(), e.getMessage()), e);
            }
        }
    }
    
    protected final void createClassDescriptors(final MappingRoot mapping)
    throws MappingException {
        // Load the mapping for all the classes. This is always returned
        // in the same order as it appeared in the mapping file.
        Enumeration<? extends ClassMapping> enumeration = mapping.enumerateClassMapping();

        List<ClassMapping> retryList = new ArrayList<ClassMapping>();
        while (enumeration.hasMoreElements()) {
            ClassMapping clsMap = enumeration.nextElement();
            try {
                ClassDescriptor clsDesc = createClassDescriptor(clsMap);
                if (clsDesc != null) { 
                    addDescriptor(clsDesc); 
                }
            } catch (MappingException mx) {
                // save for later for possible out-of-order mapping files...
                retryList.add(clsMap);
                continue;
            }
        }

        // handle possible retries, for now we only loop once on the retries, but we
        // should change this to keep looping until we have no more success rate.
        for (ClassMapping classMapping : retryList) {
            ClassDescriptor clsDesc = createClassDescriptor(classMapping);
            if (clsDesc != null) { 
                addDescriptor(clsDesc); 
            }
        }

        // iterate over all class descriptors and resolve relations between them
        for (ClassDescriptor classDescriptor : getDescriptors()) {
            resolveRelations(classDescriptor);
        }
    }

    protected abstract ClassDescriptor createClassDescriptor(final ClassMapping clsMap)
    throws MappingException;

    /**
     * Gets the ClassDescriptor the given <code>classMapping</code> extends.
     *
     * @param clsMap The ClassMapping to find the required descriptor for.
     * @param javaClass The name of the class that is checked (this is used for
     *        generating the exception).
     * @return The ClassDescriptor the given ClassMapping extends or
     *         <code>null</code> if the given ClassMapping does not extend
     *         any.
     * @throws MappingException If the given ClassMapping extends another
     *         ClassMapping but its descriptor could not be found.
     */
    protected final ClassDescriptor getExtended(final ClassMapping clsMap,
                                                final Class<?> javaClass) throws MappingException {
        if (clsMap.getExtends() == null) { 
            return null;
        }

        ClassMapping mapping = (ClassMapping) clsMap.getExtends();
        Class<?> type = resolveType(mapping.getName());
        ClassDescriptor result = getDescriptor(type.getName());

        if (result == null) {
            throw new MappingException(
                    "mapping.extendsMissing", mapping, javaClass.getName());
        }

        if (!result.getJavaClass().isAssignableFrom(javaClass)) {
            throw new MappingException("mapping.classDoesNotExtend",
                    javaClass.getName(), result.getJavaClass().getName());
        }

        return result;
    }

    /**
     * Gets the ClassDescriptor the given <code>classMapping</code> depends
     * on.
     *
     * @param clsMap The ClassMapping to find the required ClassDescriptor for.
     * @param javaClass The name of the class that is checked (this is used for
     *        generating the exception).
     * @return The ClassDescriptor the given ClassMapping depends on or
     *         <code>null</code> if the given ClassMapping does not depend on
     *         any.
     * @throws MappingException If the given ClassMapping depends on another
     *         ClassMapping but its descriptor could not be found.
     */
    protected final ClassDescriptor getDepended(final ClassMapping clsMap,
                                                final Class<?> javaClass) throws MappingException {
        if (clsMap.getDepends() == null) { 
            return null; 
        }

        ClassMapping mapping = (ClassMapping) clsMap.getDepends();
        Class<?> type = resolveType(mapping.getName());
        ClassDescriptor result = getDescriptor(type.getName());

        if (result == null) {
            throw new MappingException(
                    "Depends not found: " + mapping + " " + javaClass.getName());
        }

        return result;
    }

    /**
     * Checks all given fields for name equality and throws a MappingException if at
     * least two fields have the same name.
     *
     * @param fields The fields to be checked.
     * @param cls Class that is checked (this is used for generating the exception).
     * @throws MappingException If at least two fields have the same name.
     */
    protected final void checkFieldNameDuplicates(final FieldDescriptor[] fields,
                                                  final Class<?> cls) throws MappingException {
        for (int i = 0; i < fields.length - 1; i++) {
            String fieldName = fields[i].getFieldName();
            for (int j = i + 1; j < fields.length; j++) {
                if (fieldName.equals(fields[j].getFieldName())) {
                    throw new MappingException("The field " + fieldName
                            + " appears twice in the descriptor for " + cls.getName());
                }
            }
        }
    }

    protected abstract void resolveRelations(final ClassDescriptor clsDesc);

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

    /**
     * Returns the Java class for the named type. The type name can be one of the
     * accepted short names (e.g. <tt>integer</tt>) or the full Java class name (e.g.
     * <tt>java.lang.Integer</tt>). If the short name is used, the primitive type might
     * be returned.
     */
    protected final Class<?> resolveType(final String typeName)
    throws MappingException {
        try {
            return Types.typeFromName(getClassLoader(), typeName);
        } catch (ClassNotFoundException ex) {
            throw new MappingException("mapping.classNotFound", typeName);
        }
    }

    /**
     * Create field descriptors. The class mapping information is used to create
     * descriptors for all the fields in the class, except for container fields.
     * Implementations may extend this method to create more suitable descriptors, or
     * create descriptors only for a subset of the fields.
     *
     * @param clsMap The class to which the fields belong.
     * @param javaClass The field mappings.
     * @throws MappingException An exception indicating why mapping for the class cannot
     *         be created.
     */
    protected final FieldDescriptorImpl[] createFieldDescriptors(final ClassMapping clsMap,
                                                                     final Class<?> javaClass) throws MappingException {
        FieldMapping[] fldMap = null;

        if (clsMap.getClassChoice() != null) {
            fldMap = clsMap.getClassChoice().getFieldMapping();
        }

        if ((fldMap == null) || (fldMap.length == 0)) {
            return new FieldDescriptorImpl[0];
        }

        FieldDescriptorImpl[] fields = new FieldDescriptorImpl[fldMap.length];
        for (int i = 0; i < fldMap.length; i++) {
            fields[i] = createFieldDesc(javaClass, fldMap[i]);

            // set identity flag
            fields[i].setIdentity(fldMap[i].getIdentity());
        }

        return fields;
    }

    /**
     * Gets the top-most (i.e. without any further 'extends') extends of the given
     * <code>classMapping</code>.
     *
     * @param clsMap The ClassMapping to get the origin for.
     * @return The top-most extends of the given ClassMapping or the ClassMapping itself
     *         if it does not extend any other ClassMapping.
     */
    protected final ClassMapping getOrigin(final ClassMapping clsMap) {
        ClassMapping result = clsMap;

        while (result.getExtends() != null) {
            result = (ClassMapping) result.getExtends();
        }

        return result;
    }

    protected final FieldDescriptor[] divideFieldDescriptors(final FieldDescriptor[] fields,
            final String[] ids, final FieldDescriptor[] identities) {
        List<FieldDescriptor> fieldList = new ArrayList<FieldDescriptor>(fields.length);

        for (int i = 0; i < fields.length; i++) {
            FieldDescriptor field = fields[i];
            final int index = getIdColumnIndex(field, ids);
            if (index == -1) {
                // copy non identity field from list of fields.
                fieldList.add(field);
            } else {
                if (field instanceof FieldDescriptorImpl) {
                    ((FieldDescriptorImpl) field).setRequired(true);
                }
                if (field.getHandler() instanceof FieldHandlerImpl) {
                    ((FieldHandlerImpl) field.getHandler()).setRequired(true);
                }

                identities[index] = field;
            }
        }

        // convert regularFieldList into array
        FieldDescriptor[] result = new FieldDescriptor[fieldList.size()];
        return fieldList.toArray(result);
    }

    /**
     * Finds the index in the given <code>idColumnNames</code> that has the same name as
     * the given <code>field</code>.
     *
     * @param field The FieldDescriptor to find the column index for.
     * @param ids The id columnNames available.
     * @return The index of the id column name that matches the given field's name or
     *         <code>-1</code> if no such id column name exists.
     */
    protected int getIdColumnIndex(FieldDescriptor field, String[] ids) {
        for (int i = 0; i < ids.length; i++) {
            if (field.getFieldName().equals(ids[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Creates a single field descriptor. The field mapping is used to create a new stock
     * {@link FieldDescriptor}. Implementations may extend this class to create a more
     * suitable descriptor.
     *
     * @param javaClass The class to which the field belongs.
     * @param fieldMap The field mapping information.
     * @return The field descriptor.
     * @throws MappingException The field or its accessor methods are not
     *         found, not accessible, not of the specified type, etc.
     */
    protected FieldDescriptorImpl createFieldDesc(final Class<?> javaClass,
                                                      final FieldMapping fieldMap) throws MappingException {
        String fieldName = fieldMap.getName();

        // If the field type is supplied, grab it and use it to locate the field/accessor.
        Class<?> fieldType = null;
        if (fieldMap.getType() != null) {
            fieldType = resolveType(fieldMap.getType());
        }

        // If the field is declared as a collection, grab the collection type as
        // well and use it to locate the field/accessor.
        CollectionHandler collectionHandler = null;
        if (fieldMap.getCollection() != null) {
            String colTypeName = fieldMap.getCollection().toString();
            Class<?> collectionType = CollectionHandlers.getCollectionType(colTypeName);
            collectionHandler = CollectionHandlers.getHandler(collectionType);
        }

        TypeInfo typeInfo = getTypeInfo(fieldType, collectionHandler, fieldMap);

        ExtendedFieldHandler exfHandler = null;
        FieldHandler handler = null;

        // Check for user supplied FieldHandler
        if (fieldMap.getHandler() != null) {
            handler = getFieldHandler(fieldMap);

            // ExtendedFieldHandler?
            if (handler instanceof ExtendedFieldHandler) {
                exfHandler = (ExtendedFieldHandler) handler;
            }

            // Fix for CastorJDO from Steve Vaughan, CastorJDO requires FieldHandlerImpl
            // or a ClassCastException will be thrown... [KV 20030131 - also make sure
            // this new handler doesn't use it's own CollectionHandler otherwise it'll
            // cause unwanted calls to the getValue method during unmarshalling]
            collectionHandler = typeInfo.getCollectionHandler();
            typeInfo.setCollectionHandler(null);
            handler = new FieldHandlerImpl(handler, typeInfo);
            typeInfo.setCollectionHandler(collectionHandler);
            // End Castor JDO fix
        }

        boolean generalized = (exfHandler instanceof GeneralizedFieldHandler);

        // If generalized we need to change the fieldType to whatever is specified in the
        // GeneralizedFieldHandler so that the correct getter/setter methods can be found
        if (generalized) {
            fieldType = ((GeneralizedFieldHandler)exfHandler).getFieldType();
        }

        if (generalized || (handler == null)) {
            // Create TypeInfoRef to get new TypeInfo from call to createFieldHandler
            FieldHandler custom = handler;
            TypeInfoReference typeInfoRef = new TypeInfoReference();
            typeInfoRef.typeInfo = typeInfo;
            handler = createFieldHandler(javaClass, fieldType, fieldMap, typeInfoRef);
            if (custom != null) {
                ((GeneralizedFieldHandler) exfHandler).setFieldHandler(handler);
                handler = custom;
            } else {
                boolean isTypeSafeEnum = false;
                // Check for type-safe enum style classes
                if ((fieldType != null) && !isPrimitive(fieldType)) {
                    if (!hasPublicDefaultConstructor(fieldType)) {
                        Method method = getStaticValueOfMethod(fieldType);
                        if (method != null) {
                            handler = new EnumFieldHandler(fieldType, handler, method);
                            typeInfo.setImmutable(true);
                            isTypeSafeEnum = true;
                        }
                    }
                }
                // Reset proper TypeInfo
                if (!isTypeSafeEnum) { typeInfo = typeInfoRef.typeInfo; }
            }
        }

        FieldDescriptorImpl fieldDesc = new FieldDescriptorImpl(
                fieldName, typeInfo, handler, fieldMap.getTransient());

        fieldDesc.setRequired(fieldMap.getRequired());
        
        // set collection type as specified in mapping file
        fieldDesc.setCollection(fieldMap.getCollection());
        
        // set various method informations 
        fieldDesc.setComparator(fieldMap.getComparator());
        fieldDesc.setCreateMethod(fieldMap.getCreateMethod());
        fieldDesc.setGetMethod(fieldMap.getGetMethod());
        fieldDesc.setSetMethod(fieldMap.getSetMethod());
        
        // set direct access (if defined)
        fieldDesc.setDirect(fieldMap.getDirect());
        
        // extract values for 'laziness' from field mapping
        fieldDesc.setLazy(fieldMap.isLazy());
        
        // If we're using an ExtendedFieldHandler we need to set the FieldDescriptor
        if (exfHandler != null) {
            ((FieldHandlerFriend) exfHandler).setFieldDescriptor(fieldDesc);
        }

        return fieldDesc;
    }

    private FieldHandler getFieldHandler(final FieldMapping fieldMap) 
    throws MappingException {
        
        // If there is a custom field handler present in the mapping, that one
        // is returned.
        FieldHandler handler = _fieldHandlers.get(fieldMap.getHandler());
        if (handler != null) {

            if (!(handler instanceof ClonableFieldHandler)) {
                return handler;
            }
            
            FieldHandler clonedHandler = handler;
            Class<?> classToClone = handler.getClass();
            try {
                Method method = classToClone.getMethod("copyInstance", new Class[] { FieldHandler.class });
                clonedHandler = (FieldHandler) method.invoke(handler, handler);
                return clonedHandler;
            } catch (Exception ex) {
                String err = "The class '" + classToClone.getName() + "' must implement ClonableFieldHandler.";
                throw new MappingException(err);
            }
        }
        
        Class<?> handlerClass = null;
        handlerClass = resolveType(fieldMap.getHandler());

        if (!FieldHandler.class.isAssignableFrom(handlerClass)) {
            String err = "The class '" + fieldMap.getHandler() + "' must implement "
                       + FieldHandler.class.getName();
            throw new MappingException(err);
        }

        // Get default constructor to invoke. We can't use the newInstance method
        // unfortunately becaue FieldHandler overloads this method
        try {
            Constructor<?> constructor = handlerClass.getConstructor(new Class[0]);
            return (FieldHandler) constructor.newInstance(new Object[0]);
        } catch (Exception ex) {
            String err = "The class '" + handlerClass.getName()
                       + "' must have a default public constructor.";
            throw new MappingException(err);
        }
    }

    /**
     * Does the given class has a public default constructor?
     *
     * @param type Class to check for a public default constructor.
     * @return <code>true</code> if class has a public default constructor.
     */
    private boolean hasPublicDefaultConstructor(final Class<?> type) {
        try {
            Constructor<?> cons = type.getConstructor(EMPTY_ARGS);
            return Modifier.isPublic(cons.getModifiers());
        } catch (NoSuchMethodException ex) {
            return false;
        }
    }

    /**
     * Get static valueOf(String) factory method of given class.
     *
     * @param type Class to check for a static valueOf(String) factory method.
     * @return Static valueOf(String) factory method or <code>null</code> if none could
     *         be found.
     */
    private Method getStaticValueOfMethod(final Class<?> type) {
        try {
            Method method = type.getMethod(VALUE_OF, STRING_ARG);
            Class<?> returnType = method.getReturnType();
            if (returnType == null) {
                return null;
            }
            if (!type.isAssignableFrom(returnType)) { 
                return null; 
            }
            if (!Modifier.isStatic(method.getModifiers())) { 
                return null; 
            }
            return method;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }

    /**
     * Creates the FieldHandler for the given FieldMapping.
     *
     * @param javaClass the class type of the parent of the field.
     * @param fldType the Java class type for the field.
     * @param fldMap the field mapping.
     * @return the newly created FieldHandler.
     */
    protected final FieldHandler createFieldHandler(Class<?> javaClass, Class<?> fldType,
                                                    final FieldMapping fldMap,
                                                    final TypeInfoReference typeInfoRef) throws MappingException {
        // Prevent introspection of transient fields
        if (fldMap.getTransient()) {
            return new TransientFieldHandler();
        }

        Class<?> collectionType = null;
        CollectionHandler collectionHandler = null;
        boolean colRequireGetSet = true;

        String fieldName = fldMap.getName();

        // If the field is declared as a collection, grab the collection type as
        // well and use it to locate the field/accessor.
        if (fldMap.getCollection() != null) {
            String colTypeName = fldMap.getCollection().toString();
            collectionType = CollectionHandlers.getCollectionType(colTypeName);
            collectionHandler = CollectionHandlers.getHandler(collectionType);
            colRequireGetSet = CollectionHandlers.isGetSetCollection(collectionType);
            if (collectionType == Object[].class) {
                if (fldType == null) {
                    String msg = "'type' is a required attribute for field that are "
                               + "array collections: " + fieldName;
                    throw new MappingException(msg);
                }
                Object obj = Array.newInstance(fldType, 0);
                collectionType = obj.getClass();
            }
        }


        FieldHandlerImpl  handler            = null;

        // If get/set methods not specified, use field names to determine them.
        if (fldMap.getDirect()) {
            // No accessor, map field directly.
            Field field = findField(javaClass, fieldName, (collectionType == null ? fldType : collectionType));
            if (field == null) {
                throw new MappingException(
                        "mapping.fieldNotAccessible", fieldName, javaClass.getName());
            }
            if (fldType == null) {
                fldType = field.getType();
            }

            typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);

            handler = new FieldHandlerImpl(field, typeInfoRef.typeInfo);
        } else if ((fldMap.getGetMethod() == null) && (fldMap.getSetMethod() == null)) {
            // If both methods (get/set) are not specified, determine them automatically
            if (fieldName == null) {
                throw new MappingException(
                        "mapping.missingFieldName", javaClass.getName());
            }

            List<Method> getSequence = new ArrayList<Method>();
            List<Method> setSequence = new ArrayList<Method>();
            Method getMethod = null;
            Method setMethod = null;

            // Get method normally starts with "get", but may start with "is"
            // if it's a boolean.
            try {
                // Handle nested fields
                while (true) {
                    int point = fieldName.indexOf('.');
                    if (point < 0) { break; }

                    String parentField = fieldName.substring(0, point);

                    // Getter method for parent field
                    String methodName = GET_METHOD_PREFIX + capitalize(parentField);
                    Method method = javaClass.getMethod(methodName, (Class[]) null);
                    if (isAbstractOrStatic(method)) {
                        throw new MappingException("mapping.accessorNotAccessible",
                                methodName, javaClass.getName());
                    }
                    getSequence.add(method);

                    Class<?> nextClass = method.getReturnType();

                    // Setter method for parent field
                    try {
                        methodName = SET_METHOD_PREFIX + capitalize(parentField);
                        Class<?>[] types = new Class[] {nextClass};
                        method = javaClass.getMethod(methodName, types);
                        if (isAbstractOrStatic(method)) { method = null; }
                    } catch (Exception ex) {
                        method = null;
                    }
                    setSequence.add(method);

                    javaClass = nextClass;
                    fieldName = fieldName.substring(point + 1);
                }

                // Find getter method for actual field
                String methodName = GET_METHOD_PREFIX + capitalize( fieldName );
                Class<?> returnType = (collectionType == null) ? fldType : collectionType;
                getMethod = findAccessor(javaClass, methodName, returnType, true);

                // If getMethod is null, check for boolean type method prefix
                if (getMethod == null) {
                    if ((fldType == Boolean.class) || (fldType == Boolean.TYPE)) {
                        methodName = IS_METHOD_PREFIX + capitalize(fieldName);
                        getMethod = findAccessor(javaClass, methodName, returnType, true);
                    }
                }
            } catch (MappingException ex) {
                throw ex;
            } catch (Exception ex) {
                // LOG.warn("Unexpected exception", ex);
            }

            if (getMethod == null) {
                String getAccessor = GET_METHOD_PREFIX + capitalize(fieldName);
                String isAccessor = IS_METHOD_PREFIX + capitalize(fieldName);
                throw new MappingException("mapping.accessorNotFound",
                        getAccessor + "/" + isAccessor,
                        (collectionType == null ? fldType : collectionType),
                        javaClass.getName());
            }

            if ((fldType == null) && (collectionType == null)) {
                fldType = getMethod.getReturnType();
            }

            // We try to locate a set method anyway but complain only if we need one
            String methodName = SET_METHOD_PREFIX + capitalize(fieldName);
            setMethod = findAccessor(javaClass, methodName,
                    (collectionType == null ? fldType : collectionType), false);

            // If we have a collection that need both set and get but we don't have a
            // set method, we fail
            if ((setMethod == null) && (collectionType != null) && colRequireGetSet) {
                throw new MappingException("mapping.accessorNotFound", methodName,
                        (collectionType == null ? fldType : collectionType), javaClass.getName());
            }

            typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);

            fieldName = fldMap.getName();
            if (fieldName == null) {
                if (getMethod == null) {
                    fieldName = setMethod.getName();
                } else {
                    fieldName = getMethod.getName();
                }
            }

            // Convert method call sequence for nested fields to arrays
            Method[] getArray = null;
            Method[] setArray = null;
            if (getSequence.size() > 0) {
                getArray = new Method[getSequence.size()];
                getArray = getSequence.toArray(getArray);
                setArray = new Method[setSequence.size()];
                setArray = setSequence.toArray(setArray);
            }

            // Create handler
            handler = new FieldHandlerImpl(fieldName, getArray, setArray,
                    getMethod, setMethod, typeInfoRef.typeInfo);

            if (setMethod != null) {
                if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
                    handler.setAddMethod(setMethod);
                }
            }
        } else {
            Method getMethod = null;
            Method setMethod = null;

            // First look up the get accessors
            if (fldMap.getGetMethod() != null) {
                Class<?> rtype = fldType;
                if (collectionType != null) {
                    String methodName = fldMap.getGetMethod();
                    if (methodName.startsWith(ENUM_METHOD_PREFIX)) {
                        // An enumeration method must really return a enumeration.
                        rtype = Enumeration.class;
                    } else if (methodName.startsWith(ITER_METHOD_PREFIX)) {
                        // An iterator method must really return a iterator.
                        rtype = Iterator.class;
                    } else {
                        rtype = collectionType;
                    }
                }

                getMethod = findAccessor(javaClass, fldMap.getGetMethod(), rtype, true);
                if (getMethod == null) {
                    throw new MappingException("mapping.accessorNotFound",
                            fldMap.getGetMethod(), rtype, javaClass.getName());
                }

                if ((fldType == null) && (collectionType == null)) {
                    fldType = getMethod.getReturnType();
                }
            }

            // Second look up the set/add accessor
            if (fldMap.getSetMethod() != null) {
                String methodName = fldMap.getSetMethod();
                Class<?> type = fldType;
                if (collectionType != null) {
                    if (!methodName.startsWith(ADD_METHOD_PREFIX)) {
                        type = collectionType;
                    }
                }

                // Set via constructor?
                if (methodName.startsWith("%")) {
                    // Validate index value
                    int index = 0;

                    String temp = methodName.substring(1);
                    try {
                        index = Integer.parseInt(temp);
                    } catch(NumberFormatException ex) {
                        throw new MappingException("mapping.invalidParameterIndex", temp);
                    }

                    if ((index < 1) || (index > 9)) {
                        throw new MappingException("mapping.invalidParameterIndex", temp);
                    }
                } else {
                    setMethod = findAccessor(javaClass, methodName, type , false);
                    if (setMethod == null) {
                        throw new MappingException("mapping.accessorNotFound",
                                methodName, type, javaClass.getName());
                    }

                    if (fldType == null) {
                        fldType = setMethod.getParameterTypes()[0];
                    }
                }
            }

            typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);

            fieldName = fldMap.getName();
            if (fieldName == null) {
                if (getMethod == null) {
                    fieldName = setMethod.getName();
                } else {
                    fieldName = getMethod.getName();
                }
            }

            // Create handler
            handler = new FieldHandlerImpl(fieldName, null, null,
                    getMethod, setMethod, typeInfoRef.typeInfo);

            if (setMethod != null) {
                if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
                    handler.setAddMethod(setMethod);
                }
            }
        }

        // If there is a create method, add it to the field handler
        String methodName = fldMap.getCreateMethod();
        if (methodName != null) {
            try {
                Method method = javaClass.getMethod(methodName, (Class[]) null);
                handler.setCreateMethod(method);
            } catch (Exception ex) {
                throw new MappingException("mapping.createMethodNotFound",
                        methodName, javaClass.getName());
            }
        } else if ((fieldName != null) && !Types.isSimpleType(fldType)) {
            try {
                methodName = CREATE_METHOD_PREFIX + capitalize(fieldName);
                Method method = javaClass.getMethod(methodName, (Class[]) null);
                handler.setCreateMethod(method);
            } catch (Exception ex) {
                // LOG.warn ("Unexpected exception", ex);
            }
        }

        // If there is an has/delete method, add them to field handler
        if (fieldName != null) {
            try {
                methodName = fldMap.getHasMethod();
                if (methodName == null) {
                    methodName = HAS_METHOD_PREFIX + capitalize(fieldName);
                }
                Method hasMethod = javaClass.getMethod(methodName, (Class[]) null);

                if ((hasMethod.getModifiers() & Modifier.STATIC) != 0) {
                    hasMethod = null;
                }

                Method deleteMethod = null;
                try {
                    methodName = DELETE_METHOD_PREFIX + capitalize(fieldName);
                    deleteMethod = javaClass.getMethod(methodName, (Class[]) null );
                    if ((deleteMethod.getModifiers() & Modifier.STATIC) != 0) {
                        deleteMethod = null;
                    }
                } catch (Exception ex) {
                    // Purposely Ignore exception we're just seeing if the method exists
                }

                handler.setHasDeleteMethod(hasMethod, deleteMethod);
            } catch (Exception ex) {
                // LOG.warn("Unexpected exception", ex);
            }
        }

        return handler;
    }

    private static boolean isAbstract(final Class<?> cls) {
        return ((cls.getModifiers() & Modifier.ABSTRACT) != 0);
    }

    private static boolean isAbstractOrStatic(final Method method) {
        return ((method.getModifiers() & Modifier.ABSTRACT) != 0)
            || ((method.getModifiers() & Modifier.STATIC) != 0);
    }

    protected TypeInfo getTypeInfo(final Class<?> fieldType,
                                   final CollectionHandler colHandler,
                                   final FieldMapping fieldMap) throws MappingException {
        return new TypeInfo(Types.typeFromPrimitive(fieldType), null, null,
                            fieldMap.getRequired(), null, colHandler, false);
    }

    /**
     * Returns the named field. Uses reflection to return the named field and check the
     * field type, if specified.
     *
     * @param javaClass The class to which the field belongs.
     * @param fieldName The name of the field.
     * @param fieldType The type of the field if known, or null.
     * @return The field, null if not found.
     * @throws MappingException The field is not accessible or is not of the
     *         specified type.
     */
    private final Field findField(final Class<?> javaClass, final String fieldName,
                                  Class<?> fieldType) throws MappingException {
        try {
            // Look up the field based on its name, make sure it's only modifier
            // is public. If a type was specified, match the field type.
            Field field = javaClass.getField(fieldName);
            if ((field.getModifiers() != Modifier.PUBLIC)
                    && (field.getModifiers() != (Modifier.PUBLIC | Modifier.VOLATILE))) {
                throw new MappingException(
                        "mapping.fieldNotAccessible", fieldName, javaClass.getName());
            }

            if (fieldType == null) {
                fieldType = Types.typeFromPrimitive(field.getType());
            } else {
                Class<?> ft1 = Types.typeFromPrimitive(fieldType);
                Class<?> ft2 = Types.typeFromPrimitive(field.getType());
                if ((ft1 != ft2) && (fieldType != Serializable.class)) {
                    throw new MappingException(
                            "mapping.fieldTypeMismatch", field, fieldType.getName());
                }
            }
            return field;
        } catch (NoSuchFieldException ex) {
            return null;
        } catch (SecurityException ex) {
            return null;
        }
    }

    /**
     * Returns the named accessor. Uses reflection to return the named accessor and
     * check the return value or parameter type, if specified.
     *
     * @param javaClass The class to which the field belongs.
     * @param methodName The name of the accessor method.
     * @param fieldType The type of the field if known, or null.
     * @param getMethod True if get method, false if set method.
     * @return The method, null if not found.
     * @throws MappingException The method is not accessible or is not of the
     *         specified type.
     */
    public static final Method findAccessor(final Class<?> javaClass,
                                            final String methodName, Class<?> fieldType,
                                            final boolean getMethod) throws MappingException {
        try {
            Method method = null;

            if (getMethod) {
                // Get method: look for the named method or prepend get to the method
                // name. Look up the field and potentially check the return type.
                method = javaClass.getMethod(methodName, new Class[0]);

                // The MapItem is used to handle the contents of maps. Since the MapItem
                // has to use Object for its methods we cannot (but also don't have to)
                // check for correct types.
                if (javaClass == MapItem.class) {
                    if (methodName.equals("getKey")) { return method; }
                    if (methodName.equals("getValue")) { return method; }
                }

                if (fieldType == null) {
                    fieldType = Types.typeFromPrimitive(method.getReturnType());
                } else {
                    fieldType = Types.typeFromPrimitive(fieldType);
                    Class<?> returnType = Types.typeFromPrimitive(method.getReturnType());

                    //-- First check against whether the declared type is
                    //-- an interface or abstract class. We also check
                    //-- type as Serializable for CMP 1.1 compatibility.
                    if (fieldType.isInterface()
                            || ((fieldType.getModifiers() & Modifier.ABSTRACT) != 0)
                            || (fieldType == java.io.Serializable.class)) {

                        if (!fieldType.isAssignableFrom(returnType)) {
                            throw new MappingException(
                                    "mapping.accessorReturnTypeMismatch",
                                    method, fieldType.getName());
                        }
                    } else {
                        if (!returnType.isAssignableFrom(fieldType)) {
                            throw new MappingException(
                                    "mapping.accessorReturnTypeMismatch",
                                    method, fieldType.getName());
                        }
                    }
                }
            } else {
                // Set method: look for the named method or prepend set to the method
                // name. If the field type is know, look up a suitable method. If the
                // field type is unknown, lookup the first method with that name and
                // one parameter.
                Class<?> fieldTypePrimitive = null;
                if (fieldType != null) {
                    fieldTypePrimitive = Types.typeFromPrimitive(fieldType);
                    try {
                        method = javaClass.getMethod(methodName, new Class[] {fieldType});
                    } catch (Exception ex) {
                        try {
                            method = javaClass.getMethod(
                                    methodName, new Class[] {fieldTypePrimitive});
                        } catch (Exception ex2) {
                            // LOG.warn("Unexpected exception", ex2);
                        }
                    }

                    /* Replace above try catch block with the following one to resolve
                     * CASTOR-1141 for jdo part. After this change you can use this method
                     * also in FieldMolder and remove its findAccessor() method to omit
                     * code duplication. Having said that this introduces a problem
                     * with xmlctf that have to resolved first.
                    // first check for setter with reference type (e.g. setXxx(Integer))
                    try {
                        method = javaClass.getMethod(methodName, new Class[] {fieldTypePrimitive});
                    } catch (Exception ex) {
                        // if setter for reference type could not be found
                        // try to find one for primitive type (e.g. setXxx(int))
                        try {
                            method = javaClass.getMethod(methodName, new Class[] {fieldType});
                        } catch (Exception ex2) {
                            // LOG.warn("Unexpected exception", ex2);
                        }
                    }
                    */
                }

                if (method == null) {
                    Method[] methods = javaClass.getMethods();
                    for (int i = 0; i < methods.length; ++i) {
                        if (methods[i].getName().equals(methodName)) {
                            Class<?>[] paramTypes = methods[i].getParameterTypes();
                            if (paramTypes.length != 1) { 
                                continue; 
                            }

                            Class<?> paramType = Types.typeFromPrimitive(paramTypes[0]);

                            if (fieldType == null) {
                                method = methods[i];
                                break;
                            } else if (paramType.isAssignableFrom(fieldTypePrimitive)) {
                                method = methods[i];
                                break;
                            } else if (fieldType.isInterface() || isAbstract(fieldType)) {
                                if (fieldTypePrimitive.isAssignableFrom(paramType)) {
                                    method = methods[i];
                                    break;
                                }
                            }
                        }
                    }

                    if (method == null) { 
                        return null; 
                    }
                }
            }

            // Make sure method is public and not static.
            // (note: Class.getMethod() returns only public methods).
            if ((method.getModifiers() & Modifier.STATIC) != 0) {
                throw new MappingException(
                        "mapping.accessorNotAccessible", methodName, javaClass.getName());
            }
            return method;
        } catch (MappingException ex) {
            throw ex;
        } catch (Exception ex) {
            return null;
        }
    }

    private static final String capitalize(final String name) {
        char first = name.charAt(0);
        if (Character.isUpperCase(first)) { 
            return name; 
        }
        return Character.toUpperCase(first) + name.substring(1);
    }

    /**
     * Returns a list of column names that are part of the identity.
     *
     * @param ids Known identity names.
     * @param clsMap The class mapping.
     * @return List of identity column names.
     */
    public static final String[] getIdentityColumnNames(final String[] ids,
            final ClassMapping clsMap) {

        String[] idNames = ids;

        if ((ids == null) || (ids.length == 0)) {
            ClassChoice classChoice = clsMap.getClassChoice();
            if (classChoice == null) { 
                classChoice = new ClassChoice(); 
            }

            FieldMapping[] fieldMappings = classChoice.getFieldMapping();

            List<String> idNamesList = new ArrayList<String>();
            for (FieldMapping fieldMapping : fieldMappings) {
                if (fieldMapping.getIdentity() == true) {
                    idNamesList.add(fieldMapping.getName());
                }
            }

            if (!idNamesList.isEmpty()) {
                idNames = new String[idNamesList.size()];
                idNames = idNamesList.toArray(idNames);
            }
        }

        return idNames;
    }

    /**
     * Returns true if the given class should be treated as a primitive
     * type
     * @return true if the given class should be treated as a primitive
     * type
     */
    protected static final boolean isPrimitive(final Class<?> type) {
        if (type.isPrimitive()) { return true; }
        if ((type == Boolean.class) || (type == Character.class)) { return true; }
        return (type.getSuperclass() == Number.class);
    }

    /**
     * A class used to by the createFieldHandler method in order to
     * save the reference of the TypeInfo that was used.
     */
    public class TypeInfoReference {
        public TypeInfo typeInfo = null;
    }

    public void setInternalContext(final InternalContext internalContext) {
        _internalContext = internalContext;
    }
    
    public InternalContext getInternalContext() {
        return _internalContext;
    }

}
