/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
// dmccann - Mar 2/2009 - 2.0 - Initial implementation
package org.eclipse.persistence.internal.oxm.schema;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Vector;

import javax.xml.namespace.QName;

import org.eclipse.persistence.core.descriptors.CoreInheritancePolicy;
import org.eclipse.persistence.core.mappings.CoreMapping;
import org.eclipse.persistence.core.mappings.converters.CoreConverter;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.XMLChoiceFieldToClassAssociation;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.mappings.AnyAttributeMapping;
import org.eclipse.persistence.internal.oxm.mappings.AnyCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.AnyObjectMapping;
import org.eclipse.persistence.internal.oxm.mappings.BinaryDataCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.BinaryDataMapping;
import org.eclipse.persistence.internal.oxm.mappings.ChoiceCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.ChoiceObjectMapping;
import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.CompositeCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.CompositeObjectMapping;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.DirectCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.DirectMapping;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.mappings.ObjectReferenceMapping;
import org.eclipse.persistence.internal.oxm.schema.model.Any;
import org.eclipse.persistence.internal.oxm.schema.model.AnyAttribute;
import org.eclipse.persistence.internal.oxm.schema.model.Attribute;
import org.eclipse.persistence.internal.oxm.schema.model.Choice;
import org.eclipse.persistence.internal.oxm.schema.model.ComplexContent;
import org.eclipse.persistence.internal.oxm.schema.model.ComplexType;
import org.eclipse.persistence.internal.oxm.schema.model.Element;
import org.eclipse.persistence.internal.oxm.schema.model.Extension;
import org.eclipse.persistence.internal.oxm.schema.model.Import;
import org.eclipse.persistence.internal.oxm.schema.model.Occurs;
import org.eclipse.persistence.internal.oxm.schema.model.Restriction;
import org.eclipse.persistence.internal.oxm.schema.model.Schema;
import org.eclipse.persistence.internal.oxm.schema.model.Sequence;
import org.eclipse.persistence.internal.oxm.schema.model.SimpleContent;
import org.eclipse.persistence.internal.oxm.schema.model.SimpleType;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.EnumTypeConverter;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
import org.eclipse.persistence.sessions.Project;

/**
 * INTERNAL:
 *  <p><b>Purpose:</b>Generate one or more EclipseLink Schema model objects based
 *  on a given list of XMLDescriptors.
 *  <p><b>Responsibilities:</b><ul>
 *  <li>Return a Map of generated EclipseLink Schema objects based on a given list of XMLDescriptors</li>
 *  </ul>
 *  <p> This class will create and populate one or more EclipseLink schema model Schema objects for a
 *  given list of EclipseLink XMLDescriptors.
 *
 *  @see Schema
 *  @see XMLDescriptor
 */
public class SchemaModelGenerator {
    protected static final String SCHEMA_FILE_NAME = "schema";
    protected static final String SCHEMA_FILE_EXT = ".xsd";
    protected static final String TEXT = "text()";
    protected static final String ID = "ID";
    protected static final String IDREF = "IDREF";
    protected static String SWAREF_LOCATION;

    private ConversionManager conversionManager;

    /**
     * The default constructor.
     */
    public SchemaModelGenerator(ConversionManager conversionManager) {
        this(conversionManager, false);
    }

    /**
     * This constructor should be used with a value of 'true' when the schemaLocation
     * attribute of the import for swaRef should be swaref.xsd.  This is useful when
     * the user has a local copy of the swaRef schema and wants to use it when access
     * to external URLs is not available.  The default value is:
     * "http://ws-i.org/profiles/basic/1.1/swaref.xsd".
     */
    public SchemaModelGenerator(ConversionManager conversionManager, boolean customSwaRefSchema) {
        this.conversionManager = conversionManager;
        if (customSwaRefSchema) {
            SWAREF_LOCATION = Constants.SWA_REF.toLowerCase() + SCHEMA_FILE_EXT;
        } else {
            SWAREF_LOCATION = Constants.SWAREF_XSD;
        }
    }

    /**
     * Generates a Map of EclipseLink schema model Schema objects for a given list of XMLDescriptors.
     * The descriptors are assumed to have been initialized.  One Schema  object will be generated
     * per namespace.
     *
     * @param descriptorsToProcess list of XMLDescriptors which will be used to generate Schema objects
     * @param properties holds a namespace to Properties map containing schema settings, such as elementFormDefault
     * @param additionalGlobalElements a map of QName-Type entries identifying additional global elements to be added
     * @return a map of namespaces to EclipseLink schema model Schema objects
     * @throws DescriptorException if the reference descriptor for a composite mapping is not in the list of descriptors
     * @see Schema
     */
    public Map<String, Schema> generateSchemas(List<Descriptor> descriptorsToProcess, SchemaModelGeneratorProperties properties, Map<QName, Type> additionalGlobalElements) throws DescriptorException {
        Map<String, Schema> schemaForNamespace = generateSchemas(descriptorsToProcess, properties);
        // process any additional global elements
        if (additionalGlobalElements != null) {
            for(Entry<QName, Type> entry : additionalGlobalElements.entrySet()) {
                QName qname = entry.getKey();
                Type type = entry.getValue();
                if (type instanceof Class) {
                    Class tClass = (Class) type;
                    String nsKey = qname.getNamespaceURI();
                    Schema schema = schemaForNamespace.get(nsKey);

                    QName typeAsQName = conversionManager.schemaType(tClass);
                    if (typeAsQName == null) {
                        // not a built in type - need to get the type via schema reference
                        Descriptor desc = getDescriptorByClass(tClass, descriptorsToProcess);
                        if (desc == null) {
                            // at this point we can't determine the element type, so don't add anything
                            continue;
                        }
                        // if the schema is null generate a new one and create an import
                        if (schema == null) {
                            schema = buildNewSchema(nsKey, new org.eclipse.persistence.oxm.NamespaceResolver(), schemaForNamespace.size(), properties);
                            schemaForNamespace.put(nsKey, schema);
                            typeAsQName = desc.getSchemaReference().getSchemaContextAsQName();

                            Schema schemaForUri = schemaForNamespace.get(typeAsQName.getNamespaceURI());

                            if (!importExists(schema, schemaForUri.getTargetNamespace())) {
                                Import newImport = new Import();
                                newImport.setNamespace(schemaForUri.getTargetNamespace());
                                newImport.setSchemaLocation(schemaForUri.getName());
                                schema.getImports().add(newImport);
                            }
                        } else {
                            typeAsQName = desc.getSchemaReference().getSchemaContextAsQName(schema.getNamespaceResolver());
                        }
                    }
                    if (schema == null) {
                        schema = buildNewSchema(nsKey, new org.eclipse.persistence.oxm.NamespaceResolver(), schemaForNamespace.size(), properties);
                        schemaForNamespace.put(nsKey, schema);
                    }
                    Element element = new Element();
                    element.setName(qname.getLocalPart());
                    element.setType(getSchemaTypeString(typeAsQName, schema));
                    schema.addTopLevelElement(element);
                }
            }
        }
        return schemaForNamespace;
    }

    /**
     * Generates a Map of EclipseLink schema model Schema objects for a given list of XMLDescriptors.
     * The descriptors are assumed to have been initialized.  One Schema  object will be generated
     * per namespace.
     *
     * @param descriptorsToProcess list of XMLDescriptors which will be used to generate Schema objects
     * @param properties holds a namespace to Properties map containing schema settings, such as elementFormDefault
     * @return a map of namespaces to EclipseLink schema model Schema objects
     * @throws DescriptorException if the reference descriptor for a composite mapping is not in the list of descriptors
     * @see Schema
     */
    public Map<String, Schema> generateSchemas(List<Descriptor> descriptorsToProcess, SchemaModelGeneratorProperties properties) throws DescriptorException {
        HashMap<String, Schema> schemaForNamespace = new HashMap<String, Schema>();
        Schema workingSchema = null;
        if (properties == null) {
            properties = new SchemaModelGeneratorProperties();
        }
        // set up schemas for the descriptors
        for (Descriptor desc : descriptorsToProcess) {
            String namespace;
            XMLSchemaReference schemaRef = desc.getSchemaReference();
            if (schemaRef != null) {
                namespace = schemaRef.getSchemaContextAsQName(desc.getNamespaceResolver()).getNamespaceURI();
                workingSchema = getSchema(namespace, desc.getNamespaceResolver(), schemaForNamespace, properties);
                addNamespacesToWorkingSchema(desc.getNamespaceResolver(), workingSchema);
            } else {
                // at this point there is no schema reference set, but if a descriptor has a
                // default root element set we will need to generate a global element for it
                for (DatabaseTable table : (Vector<DatabaseTable>)desc.getTables()) {
                    namespace = getDefaultRootElementAsQName(desc, table.getName()).getNamespaceURI();
                    workingSchema = getSchema(namespace, desc.getNamespaceResolver(), schemaForNamespace, properties);
                    addNamespacesToWorkingSchema(desc.getNamespaceResolver(), workingSchema);
                }
            }
        }
        // process the descriptors
        for (Descriptor xdesc : descriptorsToProcess) {
            processDescriptor(xdesc, schemaForNamespace, workingSchema, properties, descriptorsToProcess);
        }
        // return the generated schema(s)
        return schemaForNamespace;
    }

    /**
     * Generates a Map of EclipseLink schema model Schema objects for a given list of XMLDescriptors.
     * The descriptors are assumed to have been initialized.  One Schema  object will be generated
     * per namespace.
     *
     * @param descriptorsToProcess list of XMLDescriptors which will be used to generate Schema objects
     * @param properties holds a namespace to Properties map containing schema settings, such as elementFormDefault
     * @param additionalGlobalElements a map of QName-Type entries identifying additional global elements to be added
     * @return a map of namespaces to EclipseLink schema model Schema objects
     * @throws DescriptorException if the reference descriptor for a composite mapping is not in the list of descriptors
     * @see Schema
     */
    public Map<String, Schema> generateSchemas(List<Descriptor> descriptorsToProcess, SchemaModelGeneratorProperties properties, SchemaModelOutputResolver outputResolver, Map<QName, Type> additionalGlobalElements) throws DescriptorException {
        Map<String, Schema> schemas = generateSchemas(descriptorsToProcess, properties, additionalGlobalElements);
        // write out the generates schema(s) via the given output resolver
        Project proj = new SchemaModelProject();
        XMLContext context = new XMLContext(proj);
        XMLMarshaller marshaller = context.createMarshaller();
        Descriptor schemaDescriptor = (Descriptor)proj.getDescriptor(Schema.class);
        int schemaCount = 0;
        for (Entry<String, Schema> entry : schemas.entrySet()) {
            Schema schema = entry.getValue();
            try {
                NamespaceResolver schemaNamespaces = schema.getNamespaceResolver();
                schemaNamespaces.put(Constants.SCHEMA_PREFIX, "http://www.w3.org/2001/XMLSchema");
                schemaDescriptor.setNamespaceResolver(schemaNamespaces);
                javax.xml.transform.Result target = outputResolver.createOutput(schema.getTargetNamespace(), schema.getName());
                marshaller.marshal(schema, target);
                schemaCount++;
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return schemas;
    }

    /**
     * Generates a Map of EclipseLink schema model Schema objects for a given list of XMLDescriptors.
     * The descriptors are assumed to have been initialized.  One Schema  object will be generated
     * per namespace.
     *
     * @param descriptorsToProcess list of XMLDescriptors which will be used to generate Schema objects
     * @param properties holds a namespace to Properties map containing schema settings, such as elementFormDefault
     * @return a map of namespaces to EclipseLink schema model Schema objects
     * @throws DescriptorException if the reference descriptor for a composite mapping is not in the list of descriptors
     * @see Schema
     */
    public Map<String, Schema> generateSchemas(List<Descriptor> descriptorsToProcess, SchemaModelGeneratorProperties properties, SchemaModelOutputResolver outputResolver) throws DescriptorException {
        return generateSchemas(descriptorsToProcess, properties, outputResolver, null);
    }

    /**
     * Process a given descriptor.  Global complex types will be generated for based on
     * schema context, and global elements based on default root element.
     *
     * @param desc
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     */
    protected void processDescriptor(Descriptor desc, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        // determine if a simple type (or complex type with simple content) or complex type is required
        boolean simple = isSimple(desc);

        XMLSchemaReference schemaRef = desc.getSchemaReference();
        if (schemaRef != null) {
            if (schemaRef.getType() == org.eclipse.persistence.platform.xml.XMLSchemaReference.COMPLEX_TYPE) {
                workingSchema.addTopLevelComplexTypes(buildComplexType(false, desc, schemaForNamespace, workingSchema, properties, descriptors));
            } else if (schemaRef.getType() == org.eclipse.persistence.platform.xml.XMLSchemaReference.SIMPLE_TYPE) {
                workingSchema.addTopLevelSimpleTypes(buildSimpleType(desc, workingSchema, true));
            } else if (schemaRef.getType() == org.eclipse.persistence.platform.xml.XMLSchemaReference.ELEMENT) {
                workingSchema.addTopLevelElement(buildElement(desc, schemaForNamespace, workingSchema, properties, descriptors, simple));
            }

            for (DatabaseTable table :  (Vector<DatabaseTable>)desc.getTables()) {
                String localName = getDefaultRootElementAsQName(desc, table.getName()).getLocalPart();
                // don't overwrite existing top level elements
                if (workingSchema.getTopLevelElements().get(localName) != null) {
                    continue;
                }
                Element topLevelElement = new Element();
                topLevelElement.setName(localName);

                QName qname = schemaRef.getSchemaContextAsQName(workingSchema.getNamespaceResolver());
                String elementType = qname.getLocalPart();
                String elementTypeUri = qname.getNamespaceURI();
                String elementTypePrefix = workingSchema.getNamespaceResolver().resolveNamespaceURI(elementTypeUri);
                if (elementTypePrefix != null) {
                    elementType = elementTypePrefix + Constants.COLON + elementType;
                }

                topLevelElement.setType(elementType);
                workingSchema.addTopLevelElement(topLevelElement);
            }
        } else {
            // here we have a descriptor that does not have a schema reference set, but since
            // there is a default root element set we need to generate a global element
            for (DatabaseTable table :  (Vector<DatabaseTable>)desc.getTables()) {
                String localName = getDefaultRootElementAsQName(desc, table.getName()).getLocalPart();
                // a global element may have been created while generating an element ref
                if (workingSchema.getTopLevelElements().get(localName) == null) {
                    Element topLevelElement = new Element();
                    topLevelElement.setName(localName);
                    if (simple) {
                        if (isComplexTypeWithSimpleContentRequired(desc)) {
                            topLevelElement.setComplexType(buildComplexTypeWithSimpleContent(desc, schemaForNamespace, workingSchema, properties, descriptors));
                        } else {
                            topLevelElement.setSimpleType(buildSimpleType(desc, workingSchema, false));
                        }
                    } else {
                        topLevelElement.setComplexType(buildComplexType(true, desc, schemaForNamespace, workingSchema, properties, descriptors));
                    }
                    workingSchema.addTopLevelElement(topLevelElement);
                }
            }
        }
    }

    /**
     * Return the Schema for a given namespace.  If one doesn't exist, a new one will
     * be created and returned.
     *
     * @param uri
     * @param nr
     * @param schemaForNamespace
     * @param properties
     * @return
     * @see Schema
     */
    protected Schema getSchema(String uri, NamespaceResolver nr, HashMap<String, Schema> schemaForNamespace, SchemaModelGeneratorProperties properties) {
        Schema schema = schemaForNamespace.get(uri);
        if (schema == null) {
            schema = buildNewSchema(uri, nr, schemaForNamespace.size(), properties);
            schemaForNamespace.put(uri, schema);
        }
        return schema;
    }

    /**
     * Create and return an Element for a given XMLDescriptor.
     *
     * @param desc
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @param simple
     * @return
     */
    protected Element buildElement(Descriptor desc,  HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors, boolean simple) {
        Element element = new Element();
        element.setName(desc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()).getLocalPart());
        if (simple) {
            if (isComplexTypeWithSimpleContentRequired(desc)) {
                element.setComplexType(buildComplexTypeWithSimpleContent(desc, schemaForNamespace, workingSchema, properties, descriptors));
            } else {
                element.setSimpleType(buildSimpleType(desc, workingSchema, false));
            }
        } else {
            element.setComplexType(buildComplexType(true, desc, schemaForNamespace, workingSchema, properties, descriptors));
        }

        return element;
    }

    /**
     * Create and return a SimpleType for a given XMLDescriptor.
     *
     * @param desc
     * @param workingSchema
     * @return
     */
    protected SimpleType buildSimpleType(Descriptor desc, Schema workingSchema, boolean global) {
        SimpleType st;
        if (global) {
            st = buildNewSimpleType(desc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()).getLocalPart());
        } else {
            st = new SimpleType();
        }

        CoreMapping mapping = (CoreMapping)desc.getMappings().get(0);
        QName qname = conversionManager.schemaType(mapping.getAttributeClassification());
        String baseType = qname.getLocalPart();

        if (qname.getNamespaceURI() != null) {
            String prefix = workingSchema.getNamespaceResolver().resolveNamespaceURI(qname.getNamespaceURI());
            if (prefix == null) {
                prefix = workingSchema.getNamespaceResolver().generatePrefix();
                workingSchema.getNamespaceResolver().put(prefix, qname.getNamespaceURI());
            }
            baseType = prefix + Constants.COLON + baseType;
        }

        Restriction restriction = new Restriction();
        restriction.setBaseType(baseType);
        st.setRestriction(restriction);
        return st;
    }

    /**
     * Create and return a SimpleType with name set to the given name.
     *
     * @param name
     * @return
     */
    protected SimpleType buildNewSimpleType(String name) {
        SimpleType st = new SimpleType();
        st.setName(name);
        return st;
    }

    /**
     * Create and return a ComplexType for a given XMLDescriptor.  Assumes that the descriptor has a schema context
     * set.
     *
     * @param anonymous
     * @param desc
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @return
     */
    protected ComplexType buildComplexType(boolean anonymous, Descriptor desc,  HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        ComplexType ct = new ComplexType();
        if (!anonymous) {
            ct.setName(desc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()).getLocalPart());
        }

        CoreInheritancePolicy inheritancePolicy = desc.getInheritancePolicyOrNull();
        Extension extension = null;
        if (inheritancePolicy != null && inheritancePolicy.getParentClass() != null) {
            extension = new Extension();
            extension.setBaseType(desc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()).getLocalPart());
            ComplexContent complexContent = new ComplexContent();
            complexContent.setExtension(extension);
            ct.setComplexContent(complexContent);
        }
        Sequence seq = new Sequence();
        for (CoreMapping mapping : (Vector<CoreMapping>)desc.getMappings()) {
            processMapping(mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors);
        }
        if (extension != null) {
            extension.setSequence(seq);
        } else {
            ct.setSequence(seq);
        }
        return ct;
    }

    /**
     * Create and return a ComplexType containing simple content for a given XMLDescriptor.  Assumes
     * that the descriptor has a schema context set.
     *
     * @param desc
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @return
     */
    private ComplexType buildComplexTypeWithSimpleContent(Descriptor desc,  HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        ComplexType ct = new ComplexType();
        SimpleContent sc = new SimpleContent();
        Extension extension = new Extension();
        sc.setExtension(extension);
        ct.setSimpleContent(sc);
        for (CoreMapping mapping : (Vector<CoreMapping>)desc.getMappings()) {
            Field xFld = (Field) mapping.getField();
            if (xFld.getXPath().equals(TEXT)) {
                extension.setBaseType(getSchemaTypeForDirectMapping((DirectMapping) mapping, workingSchema));
            } else if (xFld.getXPathFragment().isAttribute()) {
                String schemaTypeString = getSchemaTypeForDirectMapping((DirectMapping) mapping, workingSchema);
                Attribute attr = buildAttribute((DirectMapping) mapping, schemaTypeString);
                extension.getOrderedAttributes().add(attr);
            }
        }
        return ct;
    }

    /**
     * Return the schema type for a given mapping's xmlfield.  If the field does not have a schema type
     * set, the attribute classification will be used if non-null.  Otherwise, ClassConstants.STRING
     * will be returned.
     *
     * @param mapping
     * @param workingSchema
     * @return
     */
    protected String getSchemaTypeForDirectMapping(DirectMapping mapping, Schema workingSchema) {
        return getSchemaTypeForElement((Field) mapping.getField(), mapping.getAttributeClassification(), workingSchema);
    }

    /**
     * Return the schema type for a given xmlfield.  If the field does not have a schema type set,
     * the attribute classification will be used if non-null.  Otherwise, ClassConstants.STRING
     * will be returned.
     *
     * @param xmlField
     * @param attrClass
     * @param workingSchema
     * @return
     */
    protected String getSchemaTypeForElement(Field xmlField, Class attrClass, Schema workingSchema) {
        String schemaTypeString = null;
        QName schemaType = xmlField.getSchemaType();
        if (schemaType != null) {
            schemaTypeString = getSchemaTypeString(schemaType, workingSchema);
        } else {
            if (attrClass != null && !attrClass.equals(CoreClassConstants.STRING)) {
                QName qName = conversionManager.schemaType(attrClass);
                if (qName != null) {
                    schemaTypeString = getSchemaTypeString(qName, workingSchema);
                }
            } else {
                // default to string
                schemaTypeString = getSchemaTypeString(Constants.STRING_QNAME, workingSchema);
            }
        }
        return schemaTypeString;
    }

    /**
     * Return the descriptor from the list whose java class name matches
     * javaClassName.  If none exists null will be returned.
     *
     * @param javaClassName
     * @param descriptors
     * @return
     */
    protected Descriptor getDescriptorByName(String javaClassName, List<Descriptor> descriptors) {
        for (Descriptor xDesc : descriptors) {
            if (xDesc.getJavaClassName().equals(javaClassName)) {
                return xDesc;
            }
        }
        return null;
    }

    /**
     * Return the descriptor from the list whose java class matches
     * javaClass.  If none exists null will be returned.
     *
     * @param javaClass
     * @param descriptors
     * @return
     */
    protected Descriptor getDescriptorByClass(Class javaClass, List<Descriptor> descriptors) {
        for (Descriptor xDesc : descriptors) {
            if (xDesc.getJavaClass() != null && xDesc.getJavaClass() == javaClass) {
                return xDesc;
            }
        }
        return null;
    }

    /**
     * Adds an Any to a given sequence.  If isCollection is true, maxOccurs will
     * be set to unbounded.
     *
     * @param seq
     * @param isCollection
     * @see Any
     * @see Occurs.UNBOUNDED
     */
    protected void processAnyMapping(Sequence seq, boolean isCollection) {
        Any any = new Any();
        any.setProcessContents(Any.LAX);
        any.setMinOccurs(Occurs.ZERO);
        if (isCollection) {
            any.setMaxOccurs(Occurs.UNBOUNDED);
        }
        seq.addAny(any);
    }

    /**
     * Process a given XMLBinaryDataMapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param workingSchema
     */
    protected void processXMLBinaryDataMapping(BinaryDataMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties) {
        Field xmlField = (Field) mapping.getField();
        XPathFragment frag = xmlField.getXPathFragment();

        String schemaTypeString;
        if (mapping.isSwaRef()) {
            schemaTypeString = getSchemaTypeString(Constants.SWA_REF_QNAME, workingSchema);
            Import newImport = new Import();
            newImport.setNamespace(Constants.REF_URL);
            newImport.setSchemaLocation(SWAREF_LOCATION);
            workingSchema.getImports().add(newImport);
        } else {
            schemaTypeString = getSchemaTypeString(Constants.BASE_64_BINARY_QNAME, workingSchema);
        }

        seq = buildSchemaComponentsForXPath(frag, seq, schemaForNamespace, workingSchema, properties);
        frag = getTargetXPathFragment(frag);

        Element elem = elementExistsInSequence(frag.getLocalName(), frag.getShortName(), seq);
        if (elem == null) {
            if (frag.getNamespaceURI() != null) {
                elem = handleFragNamespace(frag, schemaForNamespace, workingSchema, properties, null, schemaTypeString);
            } else {
                elem = buildElement(frag, schemaTypeString, Occurs.ZERO, null);
            }
            if (mapping.getNullPolicy().isNullRepresentedByXsiNil()) {
                elem.setNillable(true);
            }
            if (xmlField.isRequired()) {
                elem.setMinOccurs("1");
            }
            if (mapping.getMimeType() != null) {
                elem.getAttributesMap().put(Constants.EXPECTED_CONTENT_TYPES_QNAME, mapping.getMimeType());
            }
            seq.addElement(elem);
        }
    }

    /**
     * Process a given XMLBinaryDataCollectionMapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param workingSchema
     */
    protected void processXMLBinaryDataCollectionMapping(BinaryDataCollectionMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties) {
        Field xmlField = (Field) mapping.getField();
        XPathFragment frag = xmlField.getXPathFragment();

        String schemaTypeString;
        if (mapping.isSwaRef()) {
            schemaTypeString = getSchemaTypeString(Constants.SWA_REF_QNAME, workingSchema);
        } else {
            schemaTypeString = getSchemaTypeString(Constants.BASE_64_BINARY_QNAME, workingSchema);
        }

        seq = buildSchemaComponentsForXPath(frag, seq, schemaForNamespace, workingSchema, properties);
        frag = getTargetXPathFragment(frag);

        Element elem = elementExistsInSequence(frag.getLocalName(), frag.getShortName(), seq);
        if (elem == null) {
            if (frag.getNamespaceURI() != null) {
                elem = handleFragNamespace(frag, schemaForNamespace, workingSchema, properties, null, schemaTypeString);
                elem.setMaxOccurs(Occurs.UNBOUNDED);
            } else {
                elem = buildElement(frag, schemaTypeString, Occurs.ZERO, Occurs.UNBOUNDED);
            }
            if (mapping.getNullPolicy().isNullRepresentedByXsiNil()) {
                elem.setNillable(true);
            }
            if (xmlField.isRequired()) {
                elem.setMinOccurs("1");
            }
            if (mapping.getMimeType() != null) {
                elem.getAttributesMap().put(Constants.EXPECTED_CONTENT_TYPES_QNAME, mapping.getMimeType());
            }
            seq.addElement(elem);
        }
    }

    /**
     * Process a given XMLDirectMapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param workingSchema
     */
    protected void processXMLDirectMapping(DirectMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties) {
        Field xmlField = (Field) mapping.getField();

        XPathFragment frag = xmlField.getXPathFragment();
        if (frag.isSelfFragment()) {
            // do nothing;
            return;
        }

        // Handle ID
        boolean isPk = isFragPrimaryKey(frag, mapping);
        String schemaTypeString = null;
        if (isPk) {
            schemaTypeString = Constants.SCHEMA_PREFIX + Constants.COLON + ID;
        } else {
            schemaTypeString = getSchemaTypeForDirectMapping(mapping, workingSchema);
        }

        // Handle enumerations
        Class attributeClassification = mapping.getAttributeClassification();
        if (attributeClassification != null && Enum.class.isAssignableFrom(attributeClassification)) {
            CoreConverter converter = mapping.getConverter();
            if (converter != null && converter instanceof EnumTypeConverter) {
                processEnumeration(schemaTypeString, frag, mapping, seq, ct, workingSchema, converter);
                return;
            }
        }

        if (frag.isAttribute()) {
            Attribute attr = buildAttribute(mapping, schemaTypeString);
            if (xmlField.isRequired()) {
                attr.setUse(Attribute.REQUIRED);
            }
            ct.getOrderedAttributes().add(attr);
        } else {
            seq = buildSchemaComponentsForXPath(frag, seq, schemaForNamespace, workingSchema, properties);
            frag = getTargetXPathFragment(frag);

            Element elem = elementExistsInSequence(frag.getLocalName(), frag.getShortName(), seq);
            if (elem == null) {
                if (frag.getNamespaceURI() != null) {
                    elem = handleFragNamespace(frag, schemaForNamespace, workingSchema, properties, null, schemaTypeString);
                } else {
                    elem = buildElement(frag, schemaTypeString, Occurs.ZERO, null);
                }
                if (mapping.getNullPolicy().isNullRepresentedByXsiNil()) {
                    elem.setNillable(true);
                }
                if (xmlField.isRequired()) {
                    elem.setMinOccurs("1");
                }
                seq.addElement(elem);
            }
        }
    }

    /**
     * Process a given XMLCompositeDirectCollectionMapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param workingSchema
     */
    protected void processXMLCompositeDirectCollectionMapping(DirectCollectionMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties) {
        Field xmlField = ((Field) (mapping).getField());

        XPathFragment frag = xmlField.getXPathFragment();
        seq = buildSchemaComponentsForXPath(frag, seq, schemaForNamespace, workingSchema, properties);
        frag = getTargetXPathFragment(frag);

        String schemaTypeString = getSchemaTypeForElement(xmlField, mapping.getAttributeElementClass(), workingSchema);
        Element element = null;
        if (xmlField.usesSingleNode()) {
            SimpleType st = new SimpleType();
            org.eclipse.persistence.internal.oxm.schema.model.List list = new org.eclipse.persistence.internal.oxm.schema.model.List();

            if (schemaTypeString == null) {
                schemaTypeString = getSchemaTypeString(Constants.ANY_SIMPLE_TYPE_QNAME, workingSchema);
            }
            list.setItemType(schemaTypeString);
            st.setList(list);

            element = buildElement(xmlField.getXPathFragment(), null, Occurs.ZERO, null);
            element.setSimpleType(st);
        } else {
            if (frag.getNamespaceURI() != null) {
                element = handleFragNamespace(frag, schemaForNamespace, workingSchema, properties, element, schemaTypeString);
                element.setMaxOccurs(Occurs.UNBOUNDED);
            } else {
                element = buildElement(frag, schemaTypeString, Occurs.ZERO, Occurs.UNBOUNDED);
            }
        }

        if (mapping.getNullPolicy().isNullRepresentedByXsiNil()) {
            element.setNillable(true);
        }

        if (xmlField.isRequired()) {
            element.setMinOccurs("1");
        }

        seq.addElement(element);
    }

    /**
     * Process a given XML composite mapping - either an XMLCompositeObjectMapping, or an
     * XMLCompositeCollectionMapping.  For XMLCompositeDirectCollectionMappings the
     * processXMLCompositeDirectCollectionMapping method should be used.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @param collection
     */
    protected void processXMLCompositeMapping(CompositeObjectMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors, boolean collection) {
        Field xmlField = (Field) mapping.getField();

        String refClassName = mapping.getReferenceClassName();
        Descriptor refDesc = getDescriptorByName(refClassName, descriptors);
        if (refDesc == null) {
            throw DescriptorException.descriptorIsMissing(refClassName, (DatabaseMapping)mapping);
        }

        XPathFragment frag = xmlField.getXPathFragment();
        seq = buildSchemaComponentsForXPath(frag, seq, schemaForNamespace, workingSchema, properties);
        frag = getTargetXPathFragment(frag);

        Element element = buildElement(frag, null, Occurs.ZERO, (collection ? Occurs.UNBOUNDED : null));
        ComplexType ctype = null;

        // if the reference descriptor's schema context is null we need to generate an anonymous complex type
        if (refDesc.getSchemaReference() == null) {
            ctype = buildComplexType(true, refDesc, schemaForNamespace, workingSchema, properties, descriptors);
        } else {
            element.setType(getSchemaTypeString(refDesc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()), workingSchema));
        }

        if (frag.getNamespaceURI() != null) {
            // may need to add a global element
            element = handleFragNamespace(frag, schemaForNamespace, workingSchema, properties, element, ctype, refDesc);
        } else if (ctype != null) {
            // set an anonymous complex type
            element.setComplexType(ctype);
        }

        boolean isNillable = false;
        if (!collection) {
            isNillable = mapping.getNullPolicy().isNullRepresentedByXsiNil();
        } else {
            isNillable = ((CompositeCollectionMapping) mapping).getNullPolicy().isNullRepresentedByXsiNil();
        }
        element.setNillable(isNillable);

        if (xmlField.isRequired()) {
            element.setMinOccurs("1");
        }

        seq.addElement(element);
    }

    /**
     * Process a given XMLChoiceCollectionMapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     */
    protected void processXMLChoiceCollectionMapping(ChoiceCollectionMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        Map<Field, Class> fieldToClassMap = mapping.getFieldToClassMappings();
        List<XMLChoiceFieldToClassAssociation> choiceFieldToClassList = mapping.getChoiceFieldToClassAssociations();
        processChoiceMapping(fieldToClassMap, choiceFieldToClassList, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, true);
    }

    /**
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     */
    protected void processXMLChoiceObjectMapping(ChoiceObjectMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        Map<Field, Class> fieldToClassMap =mapping.getFieldToClassMappings();
        List<XMLChoiceFieldToClassAssociation> choiceFieldToClassList = mapping.getChoiceFieldToClassAssociations();
        processChoiceMapping(fieldToClassMap, choiceFieldToClassList, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, false);
    }

    /**
     * Process a given XMLChoiceMapping.
     *
     * @param fieldToClassMap
     * @param choiceFieldToClassList
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @param isCollection
     */
    protected void processChoiceMapping(Map<Field, Class> fieldToClassMap, List<XMLChoiceFieldToClassAssociation> choiceFieldToClassList, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors, boolean isCollection) {
        Choice theChoice = new Choice();
        if (isCollection) {
            theChoice.setMaxOccurs(Occurs.UNBOUNDED);
        }
        for (XMLChoiceFieldToClassAssociation next : choiceFieldToClassList) {
            Field field = next.getXmlField();
            Element element = buildElement(field.getXPathFragment().getShortName(), Occurs.ZERO, null);

            QName schemaTypeQName = field.getSchemaType();
            if (schemaTypeQName != null) {
                element.setType(getSchemaTypeString(schemaTypeQName, workingSchema));
            } else {
                element = processReferenceDescriptor(element, getDescriptorByClass(fieldToClassMap.get(field), descriptors), schemaForNamespace, workingSchema, properties, descriptors, field, false);
            }
            theChoice.addElement(element);
        }
        seq.addChoice(theChoice);
    }

    /**
     * Process a given XMLObjectReferenceMapping.  In the case of an XMLCollectionReferenceMapping,
     * i.e. the isCollection flag is set to true, maxOccurs will be set to 'unbounded' on any
     * source elements
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @param isCollection
     */
    protected void processXMLObjectReferenceMapping(ObjectReferenceMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors, boolean isCollection) {
        String tgtClassName = mapping.getReferenceClassName();
        Descriptor tgtDesc = getDescriptorByName(tgtClassName, descriptors);
        if (tgtDesc == null) {
            throw DescriptorException.descriptorIsMissing(tgtClassName, (DatabaseMapping)mapping);
        }

        // get the target mapping(s) to determine the appropriate type(s)
        String schemaTypeString = null;
        Map<Field, Field> associations = mapping.getSourceToTargetKeyFieldAssociations();
        for (Entry<Field, Field> entry : associations.entrySet()) {
            Field tgtField = entry.getValue();
            Vector mappings = tgtDesc.getMappings();
            // Until IDREF support is added, we want the source type to be that of the target
            //schemaTypeString = Constants.SCHEMA_PREFIX + COLON + IDREF;
            for (Enumeration mappingsNum = mappings.elements(); mappingsNum.hasMoreElements();) {
                Mapping nextMapping = (Mapping)mappingsNum.nextElement();
                if (nextMapping.getField() != null && nextMapping.getField() instanceof Field) {
                    Field xFld = (Field) nextMapping.getField();
                    if (xFld == tgtField) {
                        schemaTypeString = getSchemaTypeForElement(tgtField, nextMapping.getAttributeClassification(), workingSchema);
                    }
                }
            }
            if (schemaTypeString == null) {
                schemaTypeString = getSchemaTypeString(Constants.STRING_QNAME, workingSchema);
            }

            XPathFragment frag = entry.getKey().getXPathFragment();
            if (frag.isAttribute()) {
                Attribute attr = buildAttribute(frag, schemaTypeString);
                ct.getOrderedAttributes().add(attr);
            } else {
                Element elem = buildElement(frag, schemaTypeString, Occurs.ZERO, null);
                if (isCollection) {
                    elem.setMaxOccurs(Occurs.UNBOUNDED);
                }
                seq.addElement(elem);
            }
        }
    }

    /**
     * Process a given mapping.
     *
     * @param mapping
     * @param seq
     * @param ct
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     */
    protected void processMapping(CoreMapping mapping, Sequence seq, ComplexType ct, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors) {
        if (mapping instanceof BinaryDataMapping) {
            processXMLBinaryDataMapping((BinaryDataMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties);
        } else if (mapping instanceof BinaryDataCollectionMapping) {
            processXMLBinaryDataCollectionMapping((BinaryDataCollectionMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties);
        } else if (mapping instanceof DirectMapping) {
            processXMLDirectMapping((DirectMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties);
        } else if (mapping instanceof DirectCollectionMapping) {
            processXMLCompositeDirectCollectionMapping((DirectCollectionMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties);
        } else if (mapping instanceof CompositeCollectionMapping) {
            processXMLCompositeMapping((CompositeCollectionMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, true);
        } else if (mapping instanceof CompositeObjectMapping) {
            processXMLCompositeMapping((CompositeObjectMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, false);
        } else if (mapping instanceof AnyAttributeMapping) {
            AnyAttribute anyAttribute = new AnyAttribute();
            anyAttribute.setProcessContents(AnyAttribute.LAX);
            ct.setAnyAttribute(anyAttribute);
        } else if (mapping instanceof AnyObjectMapping) {
            processAnyMapping(seq, false);
        } else if (mapping instanceof AnyCollectionMapping) {
            processAnyMapping(seq, true);
        } else if (mapping instanceof ChoiceObjectMapping) {
            processXMLChoiceObjectMapping((ChoiceObjectMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors);
        } else if (mapping instanceof ChoiceCollectionMapping) {
            processXMLChoiceCollectionMapping((ChoiceCollectionMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors);
        } else if (mapping instanceof CollectionReferenceMapping) {
            processXMLObjectReferenceMapping((CollectionReferenceMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, true);
        } else if (mapping instanceof ObjectReferenceMapping) {
            processXMLObjectReferenceMapping((ObjectReferenceMapping) mapping, seq, ct, schemaForNamespace, workingSchema, properties, descriptors, false);
        }
    }

    /**
     * This method will generate a global element if required (based in URI and elementFormDefault) and
     * set a reference to it on a given element accordingly.  This method will typically be used for
     * direct mappings.
     *
     * @param frag
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param element
     * @param schemaTypeString
     * @return
     */
    protected Element handleFragNamespace(XPathFragment frag, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, Element element, String schemaTypeString) {
        String fragUri = frag.getNamespaceURI();
        // may need to add a global element
        Schema s = getSchema(fragUri, null, schemaForNamespace, properties);
        String targetNS = workingSchema.getTargetNamespace();
        if ((s.isElementFormDefault() && !fragUri.equals(targetNS)) || (!s.isElementFormDefault() && fragUri.length() > 0)) {
            if (s.getTopLevelElements().get(frag.getLocalName()) == null) {
                Element globalElement = new Element();
                globalElement.setName(frag.getLocalName());
                globalElement.setType(schemaTypeString);
                s.addTopLevelElement(globalElement);
            }
            element = new Element();
            element.setRef(frag.getShortName());
        } else {
            element = buildElement(frag, schemaTypeString, Occurs.ZERO, null);
        }
        return element;
    }

    /**
     * This method will generate a global element if required (based in URI and elementFormDefault) and
     * set a reference to it on a given element accordingly, or set an anonymous complex type on a given
     * element.  This method will typically be used by composite mappings.
     *
     * @param frag
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param element
     * @param ctype
     * @param refDesc
     */
    protected Element handleFragNamespace(XPathFragment frag, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, Element element, ComplexType ctype, Descriptor refDesc) {
        String fragUri = frag.getNamespaceURI();
        // may need to add a global element
        Element globalElement = null;
        Schema s = getSchema(fragUri, null, schemaForNamespace, properties);
        String targetNS = workingSchema.getTargetNamespace();
        if ((s.isElementFormDefault() && !fragUri.equals(targetNS)) || (!s.isElementFormDefault() && fragUri.length() > 0)) {
            globalElement = (Element) s.getTopLevelElements().get(frag.getLocalName());
            if (globalElement == null) {
                globalElement = new Element();
                globalElement.setName(frag.getLocalName());
                if (ctype != null) {
                    globalElement.setComplexType(ctype);
                } else {
                    globalElement.setType(getSchemaTypeString(refDesc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()), workingSchema));
                }
                s.addTopLevelElement(globalElement);
            }
            element = new Element();
            element.setMaxOccurs(Occurs.UNBOUNDED);
            element.setRef(frag.getShortName());
        }

        if (globalElement == null && ctype != null) {
            element.setComplexType(ctype);
        }
        return element;
    }

    /**
     * Return the last fragment before text() in the XPath that a given XPathFragment
     * is part of.
     *
     * @param frag
     * @return
     */
    protected XPathFragment getTargetXPathFragment(XPathFragment frag) {
        if (frag.isAttribute() || frag.isSelfFragment()) {
            return frag;
        }
        while (frag.getNextFragment() != null && !frag.getNextFragment().nameIsText()) {
            frag = frag.getNextFragment();
            if (frag.getNextFragment() == null || frag.getNextFragment().nameIsText()) {
                break;
            }
        }
        return frag;
    }

    /**
     * This method will build element/complexType/sequence components for a given XPath,
     * and return the sequence that the target element of the mapping should be added
     * to. For example, if the XPath was "contact-info/address/street/text()", street
     * would be the target.  This method defers processing of the target path element
     * to the calling method, allowing for differences in handling, such as direct
     * mappings versus composite mappings, etc.
     *
     * @param frag
     * @param seq
     * @return
     */
    protected Sequence buildSchemaComponentsForXPath(XPathFragment frag, Sequence seq, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties) {
        // the mapping will handle processing of the target fragment; return the sequence it will be added to
        if (frag.getNextFragment() == null || frag.getNextFragment().nameIsText()) {
            return seq;
        }

        Sequence currentSequence = seq;

        // if the current element exists, use it; otherwise create a new one
        Element currentElement = elementExistsInSequence(frag.getLocalName(), frag.getShortName(), currentSequence);
        boolean currentElementExists = (currentElement != null);
        if (currentElement == null) {
            currentElement = new Element();
            // don't set the element name yet, as it may end up being a ref
            ComplexType cType = new ComplexType();
            Sequence sequence = new Sequence();
            cType.setSequence(sequence);
            currentElement.setComplexType(cType);
        }

        Element globalElement = null;
        String fragUri = frag.getNamespaceURI();
        if (fragUri != null) {
            Schema s = getSchema(fragUri, null, schemaForNamespace, properties);
            String targetNS = workingSchema.getTargetNamespace();
            if ((s.isElementFormDefault() && !fragUri.equals(targetNS)) || (!s.isElementFormDefault() && fragUri.length() > 0)) {
                // must generate a global element are create a reference to it
                // if the global element exists, use it; otherwise create a new one
                globalElement = (Element) s.getTopLevelElements().get(frag.getLocalName());
                if (globalElement == null) {
                    globalElement = new Element();
                    globalElement.setName(frag.getLocalName());
                    ComplexType gCType = new ComplexType();
                    Sequence gSequence = new Sequence();
                    gCType.setSequence(gSequence);
                    globalElement.setComplexType(gCType);
                    s.addTopLevelElement(globalElement);
                }
                // if the current element doesn't exist set a ref and add it to the sequence
                if (!currentElementExists) {
                    // ref won't have a complex type
                    currentElement.setComplexType(null);
                    currentElement.setRef(frag.getShortName());
                    currentSequence.addElement(currentElement);
                    currentElementExists = true;
                }
                // make the global element current
                currentElement = globalElement;
            }
        }
        // if we didn't process a global element, and the current element isn't already in the sequence, add it
        if (!currentElementExists && globalElement == null) {
            currentElement.setName(frag.getLocalName());
            currentSequence.addElement(currentElement);
        }
        // set the correct sequence to use/return
        currentSequence = currentElement.getComplexType().getSequence();

        // don't process the last element in the path - let the calling mapping process it
        frag = frag.getNextFragment();
        if (frag.getNextFragment() != null && !frag.getNextFragment().nameIsText()) {
            Element childElt = buildElement(frag, null, Occurs.ZERO, null);
            currentSequence.addElement(childElt);
        }
        // call back into this method to process the next path element
        return buildSchemaComponentsForXPath(frag, currentSequence, schemaForNamespace, workingSchema, properties);
    }

    /**
     * Build and return an Attribute for a given XMLDirectMapping.
     *
     * @param mapping
     * @param schemaType
     * @return
     */
    protected Attribute buildAttribute(DirectMapping mapping, String schemaType) {
        XPathFragment frag = ((Field) mapping.getField()).getXPathFragment();
        Attribute attr = new Attribute();
        attr.setName(frag.getShortName());
        attr.setType(schemaType);
        return attr;
    }

    /**
     * Build and return an Attribute for a given XPathFragment.
     *
     * @param mapping
     * @param schemaType
     * @return
     */
    protected Attribute buildAttribute(XPathFragment frag, String schemaType) {
        Attribute attr = new Attribute();
        attr.setName(frag.getShortName());
        attr.setType(schemaType);
        return attr;
    }

    /**
     * Build and return an Element for a given XPathFragment.
     *
     * @param frag
     * @param schemaType
     * @param minOccurs
     * @param maxOccurs
     * @return
     */
    protected Element buildElement(XPathFragment frag, String schemaType, String minOccurs, String maxOccurs) {
        Element element = new Element();
        element.setName(frag.getLocalName());
        element.setMinOccurs(minOccurs);
        element.setMaxOccurs(frag.containsIndex() ? Occurs.UNBOUNDED : maxOccurs);
        if (schemaType != null) {
            element.setType(schemaType);
        }
        return element;
    }

    /**
     * Build and return an Element based on a given name, minOccurs and
     * maxOccurs.
     *
     * @param name
     * @param minOccurs
     * @param maxOccurs
     * @return
     */
    protected Element buildElement(String name, String minOccurs, String maxOccurs) {
        Element element = new Element();
        element.setName(name);
        element.setMinOccurs(minOccurs);
        element.setMaxOccurs(maxOccurs);
        return element;
    }

    /**
     * Indicates if two namespaces are equal.  The result is TRUE if the two URI strings are
     * equal according to the equals() method, if one is null and the other is "", or
     * both are null.
     *
     * @param uri
     * @param defaultNS
     * @return
     */
    public boolean areNamespacesEqual(String ns1, String ns2) {
        if (ns1 == null) {
            return (ns2 == null || ns2.equals(Constants.EMPTY_STRING));
        }
        return ((ns1.equals(ns2)) || (ns1.equals(Constants.EMPTY_STRING) && ns2 == null));
    }

    /**
     * Return the schema type as a string for a given QName and Schema.  The schema's
     * namespace resolver will be used to determine the prefix (if any) to use.
     *
     * @param schemaType
     * @param workingSchema
     * @return
     */
    protected String getSchemaTypeString(QName schemaType, Schema workingSchema) {
        String schemaTypeString = schemaType.getLocalPart();
        String uri = schemaType.getNamespaceURI();
        String prefix = workingSchema.getNamespaceResolver().resolveNamespaceURI(uri);
        if (prefix == null && !areNamespacesEqual(uri, workingSchema.getDefaultNamespace())) {
            if (uri.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                prefix = workingSchema.getNamespaceResolver().generatePrefix(Constants.SCHEMA_PREFIX);
            } else if (uri.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)) {
                prefix = workingSchema.getNamespaceResolver().generatePrefix(Constants.SCHEMA_INSTANCE_PREFIX);
            } else if (uri.equals(Constants.REF_URL)) {
                prefix = workingSchema.getNamespaceResolver().generatePrefix(Constants.REF_PREFIX);
            } else {
                prefix = workingSchema.getNamespaceResolver().generatePrefix();
            }
            workingSchema.getNamespaceResolver().put(prefix, uri);
        }
        if (prefix != null) {
            schemaTypeString = prefix + Constants.COLON + schemaTypeString;
        }
        return schemaTypeString;
    }

    /**
     * Adds each namespace in the given resolver to the schema.
     *
     * @param nr
     * @param workingSchema
     */
    protected void addNamespacesToWorkingSchema(NamespaceResolver nr, Schema workingSchema) {
        if (nr != null) {
            Vector<Namespace> descNamespaces = nr.getNamespaces();
            for (Namespace nextNamespace : descNamespaces) {
                workingSchema.getNamespaceResolver().put(nextNamespace.getPrefix(), nextNamespace.getNamespaceURI());
            }
        }
    }

    /**
     * Create and return a new schema for the given namespace.  ElementFormDefault and
     * AttributeFormDefault can be set via SchemaModelGeneratorProperties object.  The
     * namespace resolver's default namespace will be set if non-null.
     *
     * @param uri
     * @param nr
     * @param schemaCount
     * @param properties
     * @return
     */
    protected Schema buildNewSchema(String uri, NamespaceResolver nr, int schemaCount, SchemaModelGeneratorProperties properties) {
        Schema schema = new Schema();
        schema.setName(SCHEMA_FILE_NAME + schemaCount + SCHEMA_FILE_EXT);

        String defaultNamespace = null;
        if (nr != null) {
            defaultNamespace = nr.getDefaultNamespaceURI();
            if (defaultNamespace != null) {
                schema.setDefaultNamespace(defaultNamespace);
                schema.getNamespaceResolver().setDefaultNamespaceURI(defaultNamespace);
            }
        }

        if (!uri.equals(Constants.EMPTY_STRING)) {
            schema.setTargetNamespace(uri);
            String prefix = null;
            if (nr != null) {
                prefix = nr.resolveNamespaceURI(uri);
            }

            if (prefix == null && !uri.equals(defaultNamespace)) {
                prefix = schema.getNamespaceResolver().generatePrefix();
                schema.getNamespaceResolver().put(prefix, uri);
            }
        }

        if (properties != null) {
            // set elementFormDefault and attributeFormDefault to qualified if necessary
            Properties props = properties.getProperties(uri);
            if (props != null) {
                if (props.containsKey(SchemaModelGeneratorProperties.ELEMENT_FORM_QUALIFIED_KEY)) {
                    schema.setElementFormDefault((Boolean) props.get(SchemaModelGeneratorProperties.ELEMENT_FORM_QUALIFIED_KEY));
                }
                if (props.containsKey(SchemaModelGeneratorProperties.ATTRIBUTE_FORM_QUALIFIED_KEY)) {
                    schema.setAttributeFormDefault((Boolean) props.get(SchemaModelGeneratorProperties.ATTRIBUTE_FORM_QUALIFIED_KEY));
                }
            }
        }
        return schema;
    }

    /**
     * Determines if a given schema contains an import for a given schema name.
     *
     * @param schema
     * @param schemaName
     * @return
     */
    protected boolean importExists(Schema schema, String schemaName) {
        java.util.List<Import> imports = schema.getImports();
        for (Import nextImport : imports) {
            if (nextImport.getSchemaLocation() != null && nextImport.getSchemaLocation().equals(schemaName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return a QName representation of a qualified table name (aka default root element).  The
     * given descriptor's namespace resolver will be used to determine the correct prefix - if
     * any - to be used.
     *
     * @param desc
     * @param qualifiedTableName
     * @return
     */
    protected QName getDefaultRootElementAsQName(Descriptor desc, String qualifiedTableName) {
        QName qName = null;
        int idx = qualifiedTableName.indexOf(Constants.COLON);
        String localName = qualifiedTableName.substring(idx + 1);
        NamespaceResolver nsResolver = desc.getNamespaceResolver();
        if (nsResolver == null) {
            qName = new QName(localName);
        } else if (idx > -1) {
            String prefix = qualifiedTableName.substring(0, idx);
            String uri = nsResolver.resolveNamespacePrefix(prefix);
            qName = new QName(uri, localName);
        } else {
            if (nsResolver.getDefaultNamespaceURI() != null) {
                qName = new QName(nsResolver.getDefaultNamespaceURI(), localName);
            } else {
                qName = new QName(localName);
            }
        }
        return qName;
    }

    /**
     *
     * @param element
     * @param refDesc
     * @param schemaForNamespace
     * @param workingSchema
     * @param properties
     * @param descriptors
     * @param field
     * @param isCollection
     * @return
     */
    protected Element processReferenceDescriptor(Element element, Descriptor refDesc, HashMap<String, Schema> schemaForNamespace, Schema workingSchema, SchemaModelGeneratorProperties properties, List<Descriptor> descriptors, Field field, boolean isCollection) {
        ComplexType ctype = null;

        if (refDesc.getSchemaReference() == null) {
            ctype = buildComplexType(true, refDesc, schemaForNamespace, workingSchema, properties, descriptors);
        } else {
            element.setType(getSchemaTypeString(refDesc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()), workingSchema));
        }

        XPathFragment frag = field.getXPathFragment();
        String fragUri = frag.getNamespaceURI();
        if (fragUri != null) {
            // may need to add a global element
            Schema s = getSchema(fragUri, null, schemaForNamespace, properties);
            String targetNS = workingSchema.getTargetNamespace();
            if ((s.isElementFormDefault() && !fragUri.equals(targetNS)) || (!s.isElementFormDefault() && fragUri.length() > 0)) {
                if (s.getTopLevelElements().get(frag.getShortName()) == null) {
                    Element globalElement = new Element();
                    globalElement.setName(frag.getLocalName());
                    if (ctype != null) {
                        globalElement.setComplexType(ctype);
                    } else {
                        globalElement.setType(getSchemaTypeString(refDesc.getSchemaReference().getSchemaContextAsQName(workingSchema.getNamespaceResolver()), workingSchema));
                    }
                    s.getTopLevelElements().put(frag.getShortName(), globalElement);
                }
                element = new Element();
                element.setMinOccurs(Occurs.ZERO);
                if (isCollection) {
                    element.setMaxOccurs(Occurs.UNBOUNDED);
                }
                element.setRef(frag.getShortName());
            } else {
                element.setComplexType(ctype);
            }
        } else if (ctype != null) {
            element.setComplexType(ctype);
        }
        return element;
    }

    /**
     * Convenience method for determining if an element already exists in a given
     * sequence.  If an element exists whose name is equal to 'elementName' true
     * is returned.  False otherwise.
     *
     * @param elementName
     * @param seq
     * @return
     */
    protected Element elementExistsInSequence(String elementName, String refString, Sequence seq) {
        if (seq.isEmpty()) {
            return null;
        }
        List existingElements = seq.getOrderedElements();
        if (existingElements != null) {
            Iterator elementIt = existingElements.iterator();
            while (elementIt.hasNext()) {
                Element element;
                // could be other components in the list, like Any, but we only care about Element
                try {
                    element = (Element) elementIt.next();
                } catch (ClassCastException cce) {
                    continue;
                }
                if ((element.getRef() != null && element.getRef().equals(refString)) || (element.getName() != null && element.getName().equals(elementName))) {
                    return element;
                }
            }
        }
        return null;
    }

    /**
     * Determines if a given descriptor contains a direct mapping to "text()" indicating a
     * simple mapping.  In this case, a simple type or complex type with simple content
     * will be generated
     *
     * @param desc
     * @return
     */
    protected boolean isSimple(Descriptor desc) {
        boolean isSimple = false;
        for (CoreMapping mapping : (Vector<CoreMapping>)desc.getMappings()) {
            if (mapping.isDirectToFieldMapping()) {
                Field xFld = (Field) mapping.getField();
                if (xFld.getXPath().equals(TEXT)) {
                    isSimple = true;
                    break;
                }
            } else {
                break;
            }
        }
        return isSimple;
    }

    /**
     * Indicates if a complex type with simple content is to be generated. This is true when
     * the given descriptor has more than one mapping.  It is assumed that isSimple(desc)
     * will return true for the given descriptor, and that the descriptor will contain at most
     * one direct with a 'text()' xpath, and any additional mappings are attribute mappings.
     *
     * @param desc
     * @return
     */
    protected boolean isComplexTypeWithSimpleContentRequired(Descriptor desc) {
        return desc.getMappings().size() > 1 ? true : false;
    }

    /**
     * Process information contained within an EnumTypeConverter.  This will generate a simple
     * type containing enumeration info.
     *
     * @param schemaTypeString
     * @param frag
     * @param mapping
     * @param seq
     * @param ct
     * @param workingSchema
     * @param converter
     */
    protected void processEnumeration(String schemaTypeString, XPathFragment frag, DirectMapping mapping, Sequence seq, ComplexType ct, Schema workingSchema, CoreConverter converter) {
        Element elem = null;
        Attribute attr = null;
        if (frag.isAttribute()) {
            attr = buildAttribute(mapping, schemaTypeString);
        } else {
            elem = buildElement(frag, schemaTypeString, Occurs.ZERO, null);
        }

        Collection<String> fieldValues = ((EnumTypeConverter) converter).getAttributeToFieldValues().values();
        ArrayList facets = new ArrayList();
        for (String field : fieldValues) {
            facets.add(field);
        }

        Restriction restriction = new Restriction();
        restriction.setEnumerationFacets(facets);
        SimpleType st = new SimpleType();
        st.setRestriction(restriction);

        if (frag.isAttribute()) {
            attr.setSimpleType(st);
            ct.getOrderedAttributes().add(attr);
        } else {
            elem.setSimpleType(st);
            seq.addElement(elem);
        }
    }

    /**
     * Indicates if a given fragment is a primary key.
     *
     * @param frag
     * @param mapping
     * @return
     */
    protected boolean isFragPrimaryKey(XPathFragment frag, DirectMapping mapping) {
        /* Uncomment the following when ID support is needed
        Vector<String> pkFieldNames = mapping.getDescriptor().getPrimaryKeyFieldNames();
        if (pkFieldNames != null) {
            if (frag.isAttribute()) {
                return pkFieldNames.contains(XMLConstants.ATTRIBUTE + frag.getLocalName());
            }
            return pkFieldNames.contains(frag.getLocalName() + '/' + TEXT);
        }
        */
        return false;
    }

}
