/*******************************************************************************
 * Copyright (c) 1998, 2010 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/
package org.eclipse.persistence.jaxb.compiler;

import java.awt.Image;
import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlElementDecl.GLOBAL;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;

import org.eclipse.persistence.jaxb.javamodel.Helper;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElementWrapper;

import org.eclipse.persistence.internal.descriptors.Namespace;
import org.eclipse.persistence.internal.jaxb.many.MapValue;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.schema.model.*;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLField;

/**
 * INTERNAL:
 * <p><b>Purpose:</b>To generate Schema objects based on a map of TypeInfo objects, and some 
 * additional information gathered by the AnnotationsProcessing phase.
 * <p><b>Responsibilities:</b><ul>
 * <li>Create and maintain a collection of Schema objects based on the provided TypeInfo objects</li>
 * <li>Add additional global elements to the schema based on an optional map (for WS integration)</li>
 * <li>Should create a schema for each namespace encountered during generation.</li>
 * </ul>
 * <p>This class is used by the Generator to handle the generation of Schemas. The
 * Generator passes in a map of TypeInfo objects, generated by the Annotations processor.
 * The generated Schemas are stored in a map of keyed on Target Namespace.
 * @see org.eclipse.persistence.jaxb.compiler.TypeInfo
 * @see org.eclipse.persistence.jaxb.compiler.AnnotationsProcessor
 * @see org.eclipse.persistence.jaxb.compiler.Generator
 * @since Oracle TopLink 11.1.1.0.0
 * @author mmacivor
 */
public class SchemaGenerator {
    private HashMap<String, Schema> schemaForNamespace;
    private java.util.List<Schema> allSchemas;
    private Schema schema;
    private int schemaCount;
    private Helper helper;
    private HashMap<String, TypeInfo> typeInfo;
    private HashMap<String, NamespaceInfo> packageToNamespaceMappings;
    private HashMap<String, SchemaTypeInfo> schemaTypeInfo;
    private HashMap<String, QName> userDefinedSchemaTypes;
    private Map<String, Class> arrayClassesToGeneratedClasses;
    
    private static final String JAVAX_ACTIVATION_DATAHANDLER = "javax.activation.DataHandler";
    private static final String JAVAX_MAIL_INTERNET_MIMEMULTIPART = "javax.mail.internet.MimeMultipart";    

    public SchemaGenerator(Helper helper) {
        this.helper = helper;
    }

    public Schema generateSchema(ArrayList<JavaClass> typeInfoClasses, HashMap<String, TypeInfo> typeInfo, HashMap<String, QName> userDefinedSchemaTypes, HashMap<String, NamespaceInfo> packageToNamespaceMappings, HashMap<QName, ElementDeclaration> additionalGlobalElements, Map<String, Class> arrayClassesToGeneratedClasses) {
        this.typeInfo = typeInfo;
        this.userDefinedSchemaTypes = userDefinedSchemaTypes;
        this.packageToNamespaceMappings = packageToNamespaceMappings;
        this.schemaCount = 0;
        this.schemaTypeInfo = new HashMap<String, SchemaTypeInfo>(typeInfo.size());
        this.arrayClassesToGeneratedClasses = arrayClassesToGeneratedClasses;

        for (JavaClass javaClass : typeInfoClasses) {
            addSchemaComponents(javaClass);
        }
        populateSchemaTypes();
        if (additionalGlobalElements != null) {
            addGlobalElements(additionalGlobalElements);
        }
        return schema;
    }

    public void addSchemaComponents(JavaClass myClass) {
        // first check for type
        String myClassName = myClass.getQualifiedName();
        Element rootElement = null;
        TypeInfo info = (TypeInfo) typeInfo.get(myClassName);
        if (info.isTransient()) {
            return;
        }
        SchemaTypeInfo schemaTypeInfo = new SchemaTypeInfo();
        schemaTypeInfo.setSchemaTypeName(new QName(info.getClassNamespace(), info.getSchemaTypeName()));
        this.schemaTypeInfo.put(myClass.getQualifiedName(), schemaTypeInfo);
        NamespaceInfo namespaceInfo = this.packageToNamespaceMappings.get(myClass.getPackageName());
        if (namespaceInfo.getLocation() != null && !namespaceInfo.getLocation().equals("##generate")) {
            return;
        }
        Schema schema = getSchemaForNamespace(info.getClassNamespace());
        info.setSchema(schema);

        String typeName = info.getSchemaTypeName();
        String pfx = "";

        Property valueField = null;
        if (info.isSetXmlRootElement()) {
            //Create the root element and add it to the schema
            org.eclipse.persistence.jaxb.xmlmodel.XmlRootElement xmlRE = info.getXmlRootElement();
            rootElement = new Element();
            String elementName = xmlRE.getName();
            if (elementName.equals("##default") || elementName.equals("")) {
                if (myClassName.indexOf("$") != -1) {
                    elementName = Introspector.decapitalize(myClassName.substring(myClassName.lastIndexOf('$') + 1));
                } else {
                    elementName = Introspector.decapitalize(myClassName.substring(myClassName.lastIndexOf('.') + 1));
                }

                // the following satisfies a TCK requirement
                if (elementName.length() >= 3) {
                    int idx = elementName.length() - 1;
                    char ch = elementName.charAt(idx - 1);
                    if (Character.isDigit(ch)) {
                        char lastCh = Character.toUpperCase(elementName.charAt(idx));
                        elementName = elementName.substring(0, idx) + lastCh;
                    }
                }
            }
            rootElement.setName(elementName);
            String rootNamespace = xmlRE.getNamespace();
            if (rootNamespace.equals("##default")) {
                Schema rootElementSchema = getSchemaForNamespace(namespaceInfo.getNamespace());
                if (rootElementSchema != null) {
                    rootElementSchema.addTopLevelElement(rootElement);
                }
                schemaTypeInfo.getGlobalElementDeclarations().add(new QName(namespaceInfo.getNamespace(), rootNamespace));
                rootNamespace = namespaceInfo.getNamespace();
            } else {
                Schema rootElementSchema = getSchemaForNamespace(rootNamespace);
                if (rootElementSchema != null) {
                    rootElementSchema.addTopLevelElement(rootElement);
                }
                schemaTypeInfo.getGlobalElementDeclarations().add(new QName(rootNamespace, elementName));
            }

            // handle root-level imports/includes [schema = the type's schema]            
            Schema rootSchema = getSchemaForNamespace(rootNamespace);
            addImportIfRequired(rootSchema, schema, schema.getTargetNamespace());

            // setup a prefix, if necessary
            if (rootSchema != null && !info.getClassNamespace().equals("")) {
                pfx = getOrGeneratePrefixForNamespace(info.getClassNamespace(), rootSchema);
                pfx += ":";
            }
        }

        ArrayList<String> propertyNames = info.getPropertyNames();
        Property xmlValueProperty = info.getXmlValueProperty();
        if (info.isEnumerationType() || (propertyNames.size() == 1 && xmlValueProperty != null)) {
            SimpleType type = new SimpleType();
            //simple type case, we just need the name and namespace info
            if (typeName.equals("")) {
                //In this case, it should be a type under
                //A root elem or locally defined whenever used
                if (rootElement != null) {
                    rootElement.setSimpleType(type);
                }
            } else {
                type.setName(typeName);
                schema.addTopLevelSimpleTypes(type);
                if (rootElement != null) {
                    rootElement.setType(pfx + type.getName());
                }
            }
            //Figure out schema type and set it as Restriction
            QName restrictionType = null;
            Restriction restriction = new Restriction();
            if (info.isEnumerationType()) {
                restrictionType = ((EnumTypeInfo) info).getRestrictionBase();
                restriction.setEnumerationFacets(this.getEnumerationFacetsFor((EnumTypeInfo) info));
                restriction.setBaseType(XMLConstants.SCHEMA_PREFIX + ":" + restrictionType.getLocalPart());
                type.setRestriction(restriction);
            } else {
                valueField = info.getProperties().get(propertyNames.get(0));
                JavaClass javaType = valueField.getActualType();
                QName baseType = getSchemaTypeFor(javaType);
                String prefix = null;
                if (baseType.getNamespaceURI() != null && !baseType.getNamespaceURI().equals("")) {
                    if (baseType.getNamespaceURI().equals(XMLConstants.SCHEMA_URL)) {
                        prefix = XMLConstants.SCHEMA_PREFIX;
                    } else {
                        prefix = getPrefixForNamespace(baseType.getNamespaceURI(), schema.getNamespaceResolver());
                    }
                }
                String baseTypeName = baseType.getLocalPart();
                if (prefix != null) {
                    baseTypeName = prefix + ":" + baseTypeName;
                }
                if (valueField.isXmlList() || (valueField.getGenericType() != null)) {
                    //generate a list instead of a restriction
                    List list = new List();
                    list.setItemType(baseTypeName);
                    type.setList(list);
                } else {
                    if (helper.isAnnotationPresent(valueField.getElement(), XmlSchemaType.class)) {
                        XmlSchemaType schemaType = (XmlSchemaType) helper.getAnnotation(valueField.getElement(), XmlSchemaType.class);
                        baseType = new QName(schemaType.namespace(), schemaType.name());
                    }
                    restriction.setBaseType(baseTypeName);
                    type.setRestriction(restriction);
                }
            }
            info.setSimpleType(type);
        } else if ((valueField = this.getXmlValueFieldForSimpleContent(info)) != null) {
            ComplexType type = new ComplexType();
            SimpleContent content = new SimpleContent();
            if (typeName.equals("")) {
                if (rootElement != null) {
                    rootElement.setComplexType(type);
                }
                info.setComplexType(type);
            } else {
                type.setName(typeName);
                schema.addTopLevelComplexTypes(type);
                if (rootElement != null) {
                    rootElement.setType(pfx + type.getName());
                }
            }
            QName extensionType = getSchemaTypeFor(valueField.getType());
            if (helper.isAnnotationPresent(valueField.getElement(), XmlSchemaType.class)) {
                XmlSchemaType schemaType = (XmlSchemaType) helper.getAnnotation(valueField.getElement(), XmlSchemaType.class);
                extensionType = new QName(schemaType.namespace(), schemaType.name());
            }
            String prefix = null;
            if (extensionType.getNamespaceURI() != null && !extensionType.getNamespaceURI().equals("")) {
                if (extensionType.getNamespaceURI().equals(XMLConstants.SCHEMA_URL)) {
                    prefix = XMLConstants.SCHEMA_PREFIX;
                } else {
                    prefix = getPrefixForNamespace(extensionType.getNamespaceURI(), schema.getNamespaceResolver());
                }
            }
            String extensionTypeName = extensionType.getLocalPart();
            if (prefix != null) {
                extensionTypeName = prefix + ":" + extensionTypeName;
            }
            Extension extension = new Extension();
            extension.setBaseType(extensionTypeName);
            content.setExtension(extension);
            type.setSimpleContent(content);
            info.setComplexType(type);
        } else {
            ComplexType type = new ComplexType();
            JavaClass superClass = CompilerHelper.getNextMappedSuperClass(myClass, this.typeInfo, this.helper);

            // Handle abstract class
            if (myClass.isAbstract()) {
                type.setAbstractValue(true);
            }

            Extension extension = null;
            if (superClass != null) {
                TypeInfo parentTypeInfo = this.typeInfo.get(superClass.getQualifiedName());
                if (parentTypeInfo != null) {
                    extension = new Extension();
                    // may need to qualify the type
                    String parentPrefix = getPrefixForNamespace(parentTypeInfo.getClassNamespace(), schema.getNamespaceResolver());
                    if (parentPrefix != null) {
                        extension.setBaseType(parentPrefix + ":" + parentTypeInfo.getSchemaTypeName());
                    } else {
                        extension.setBaseType(parentTypeInfo.getSchemaTypeName());
                    }
                    ComplexContent content = new ComplexContent();
                    content.setExtension(extension);
                    type.setComplexContent(content);
                }
            }
            TypeDefParticle compositor = null;
            String[] propOrder = null;
            if (info.isSetPropOrder()) {
                propOrder = info.getPropOrder();
            }
            
            if (propOrder != null && propOrder.length == 0) {
                // Note that the spec requires an 'all' to be generated 
                // in cases where propOrder == 0, however, the TCK 
                // requires the extension case to use sequences
                if (info.hasElementRefs()) {
                    // generate a sequence to satisfy TCK
                    compositor = new Sequence();
                    if (extension != null) {
                        extension.setSequence((Sequence) compositor);
                    } else {
                        type.setSequence((Sequence) compositor);
                    }
                } else if (extension != null) {
                    compositor = new All();
                    extension.setAll((All) compositor);
                } else {
                    compositor = new All();
                    type.setAll((All) compositor);
                }
            } else {
                // generate a sequence to satisfy TCK
                compositor = new Sequence();
                if (extension != null) {
                    extension.setSequence((Sequence) compositor);
                } else {
                    type.setSequence((Sequence) compositor);
                }
            }
            if (typeName.equals("")) {
                if (rootElement != null) {
                    rootElement.setComplexType(type);
                }
                info.setComplexType(type);
                info.setCompositor(compositor);
            } else {
                type.setName(typeName);
                if (rootElement != null) {
                    rootElement.setType(pfx + type.getName());
                }
                schema.addTopLevelComplexTypes(type);
                info.setComplexType(type);
                info.setCompositor(compositor);
            }
        }
    }

    public void addToSchemaType(TypeInfo ownerTypeInfo, java.util.List<Property> properties, TypeDefParticle compositor, ComplexType type, Schema workingSchema) {
        //If there are no properties we don't want a sequence/choice or all tag written out
        if (properties.size() == 0) {
        	type.setAll(null);
        	type.setSequence(null);
        	type.setChoice(null);
        	ownerTypeInfo.setCompositor(null);
        } else {
            for (Property next : properties) {
                if (next == null) { continue; }
                Schema currentSchema = workingSchema;
                TypeDefParticle parentCompositor = compositor;
                boolean isChoice = (parentCompositor instanceof Choice);
                ComplexType parentType = type;
                
                if (!helper.isAnnotationPresent(next.getElement(), XmlTransient.class) && !next.isInverseReference()) {
                    // deal with xml-path case
                    if (next.getXmlPath() != null) {
                        // '.' xml-path requires special handling
                        if (next.getXmlPath().equals(".")) {
                            TypeInfo info = (TypeInfo) typeInfo.get(next.getActualType().getQualifiedName());
                            addToSchemaType(info, info.getPropertyList(), compositor, type, info.getSchema());
                            continue;
                        }
                        
                        // create the XPathFragment(s) for the path
                        XMLField xfld = new XMLField(next.getXmlPath());
                        xfld.setNamespaceResolver(currentSchema.getNamespaceResolver());
                        xfld.initialize();
                        // build the schema components for the xml-path
                        XmlPathResult xpr = buildSchemaComponentsForXPath(xfld.getXPathFragment(), new XmlPathResult(parentCompositor, currentSchema), (next.isAny() || next.isAnyAttribute()), isChoice, next);
                        parentCompositor = xpr.particle;
                        currentSchema = xpr.schema;
                        // if the schema component is null there is nothing to do
                        if (parentCompositor == null) {
                            continue;
                        }
                        if (parentCompositor.getOwner() instanceof ComplexType) {
                            parentType = ((ComplexType)parentCompositor.getOwner());
                        }
                    // deal with the XmlElementWrapper case
                    } else if (!isChoice && next.isSetXmlElementWrapper()) {
                        XmlElementWrapper wrapper = next.getXmlElementWrapper();
                        Element wrapperElement = new Element();
                        String name = wrapper.getName();
                        if (name.equals("##default")) {
                            name = next.getPropertyName();
                        }
                        // handle nillable
                        wrapperElement.setNillable(wrapper.isNillable());
    
                        // namespace in not the target or ##default, create a ref with min/max = 1
                        String wrapperNS = wrapper.getNamespace();
                        if (!wrapperNS.equals("##default") && !wrapperNS.equals(currentSchema.getTargetNamespace())) {
                            wrapperElement.setMinOccurs(Occurs.ONE);
                            wrapperElement.setMaxOccurs(Occurs.ONE);
    
                            String prefix = getOrGeneratePrefixForNamespace(wrapperNS, currentSchema);
                            wrapperElement.setRef(prefix + ":" + name);
                            compositor.addElement(wrapperElement);
                            // assume that the element exists and does not need to be created
                            continue;
                        } else {
                            wrapperElement.setName(name);
                            if (wrapper.isRequired()) {
                                wrapperElement.setMinOccurs(Occurs.ONE);
                            } else {
                                wrapperElement.setMinOccurs(Occurs.ZERO);
                            }
                            compositor.addElement(wrapperElement);
                            ComplexType wrapperType = new ComplexType();
                            Sequence wrapperSequence = new Sequence();
                            wrapperType.setSequence(wrapperSequence);
                            wrapperElement.setComplexType(wrapperType);
                            parentType = wrapperType;
                            parentCompositor = wrapperSequence;
                        }
                    }
                    // handle mixed content
                    if (next.isMixedContent()) {
                        parentType.setMixed(true);
                    }
                    if (next.isAttribute() && !next.isAnyAttribute()) {
                        Attribute attribute = new Attribute();
                        QName attributeName = next.getSchemaName();
                        attribute.setName(attributeName.getLocalPart());
                        if (next.isRequired()) {
                            attribute.setUse(Attribute.REQUIRED);
                        }
	                    String fixedValue = next.getFixedValue();
	                    if(fixedValue != null){
	                        attribute.setFixed(fixedValue);
	                    }
                        //Check to see if it's a collection. 
                        //Should assume XmlList on any collection?
                        JavaClass javaType = next.getType();
                        if (next.getGenericType() != null) {
                            javaType = (JavaClass) next.getGenericType();
                        }
                        TypeInfo info = (TypeInfo) typeInfo.get(next.getType().getQualifiedName());
                        String typeName = null;
                        if (next.isXmlId()) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":ID";
                        } else if (next.isXmlIdRef()) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":IDREF";
                        } else if (info != null && !info.isComplexType()) {
                            typeName = info.getSimpleType().getName();
                        } else {
                            typeName = getTypeName(next, javaType, currentSchema);                    
                        }
    
                        if (isCollectionType(next)) {
                            // assume XmlList for an attribute collection
                            SimpleType localType = new SimpleType();
                            org.eclipse.persistence.internal.oxm.schema.model.List list = new org.eclipse.persistence.internal.oxm.schema.model.List();
                            list.setItemType(typeName);
                            localType.setList(list);
                            attribute.setSimpleType(localType);
                        } else {
                            // may need to qualify the type
                            if (typeName != null && !typeName.contains(":")) {
                                if (info.getSchema() == currentSchema) {
                                    String prefix = getPrefixForNamespace(currentSchema.getTargetNamespace(), currentSchema.getNamespaceResolver());
                                    if (prefix != null) {
                                        typeName = prefix + ":" + typeName;
                                    }
                                }
                            }
                            attribute.setType(typeName);
                        }
                        String lookupNamespace = currentSchema.getTargetNamespace();
                        if (lookupNamespace == null) {
                            lookupNamespace = "";
                        }
                        NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace);
    
                        boolean isAttributeFormQualified = true;
    
                        if (namespaceInfo != null) {
                            isAttributeFormQualified = namespaceInfo.isAttributeFormQualified();
                        }
    
                        if ((isAttributeFormQualified && !attributeName.getNamespaceURI().equals(lookupNamespace))
                                || (!namespaceInfo.isAttributeFormQualified() && !attributeName.getNamespaceURI().equals(""))) {
    
                            Schema attributeSchema = this.getSchemaForNamespace(attributeName.getNamespaceURI());
                            if (attributeSchema != null && attributeSchema.getTopLevelAttributes().get(attribute.getName()) == null) {
                                //don't overwrite existing global elements and attributes.
                                attributeSchema.getTopLevelAttributes().put(attribute.getName(), attribute);
                            }
    
                            addImportIfRequired(currentSchema, attributeSchema, attributeName.getNamespaceURI());
    
                            Attribute reference = new Attribute();
                            //add an import here
                            String prefix = getPrefixForNamespace(attributeName.getNamespaceURI(), currentSchema.getNamespaceResolver());
                            
                            if (prefix == null) {
                                reference.setRef(attribute.getName());
                            } else {
                                reference.setRef(prefix + ":" + attribute.getName());
                            }
                            if (parentType.getSimpleContent() != null) {
                                parentType.getSimpleContent().getExtension().getOrderedAttributes().add(reference);
                            } else {
                                parentType.getOrderedAttributes().add(reference);
                            }
                        } else {
                            if (parentType.getSimpleContent() != null) {
                                parentType.getSimpleContent().getExtension().getOrderedAttributes().add(attribute);
                            } else if (parentType.getComplexContent() != null) {
                                parentType.getComplexContent().getExtension().getOrderedAttributes().add(attribute);
                            } else {
                                parentType.getOrderedAttributes().add(attribute);
                            }
                        }
                    } else if (next.isAnyAttribute()) {
                        AnyAttribute anyAttribute = new AnyAttribute();
                        anyAttribute.setProcessContents("skip");
                        anyAttribute.setNamespace("##other");
                        if (parentType.getSimpleContent() != null) {
                            SimpleContent content = parentType.getSimpleContent();
                            content.getRestriction().setAnyAttribute(anyAttribute);
                        } else {
                            parentType.setAnyAttribute(anyAttribute);
                        }
                    } else if (next.isChoice()) {
                        Choice choice = new Choice();
                        if (next.getGenericType() != null) {
                            choice.setMaxOccurs(Occurs.UNBOUNDED);
                        }
                        ArrayList<Property> choiceProperties = (ArrayList<Property>) next.getChoiceProperties();
                        addToSchemaType(ownerTypeInfo, choiceProperties, choice, parentType, currentSchema);
                        if (parentCompositor instanceof Sequence) {
                            ((Sequence) parentCompositor).addChoice(choice);
                        } else if (parentCompositor instanceof Choice) {
                            ((Choice) parentCompositor).addChoice(choice);
                        }
                    } else if (next.isAny()) {
                        Any any = new Any();
                        any.setNamespace("##other");
                        if (next.isLax()) {
                            any.setProcessContents(Any.LAX);
                        } else {
                            any.setProcessContents("skip");
                        }
    
                        if (isCollectionType(next)) {
                            any.setMinOccurs(Occurs.ZERO);
                            any.setMaxOccurs(Occurs.UNBOUNDED);
                        }
    
                        if (parentCompositor instanceof Sequence) {
                            ((Sequence) parentCompositor).addAny(any);
                        } else if (parentCompositor instanceof Choice) {
                            ((Choice) parentCompositor).addAny(any);
                        }
                    } else if (next.isReference()) {
                        java.util.List<ElementDeclaration> referencedElements = next.getReferencedElements();
                        if (referencedElements.size() == 1) {
                            // if only a single reference, just add the element.
                            Element element = new Element();
                            ElementDeclaration decl = referencedElements.get(0);
                            String localName = decl.getElementName().getLocalPart();
                            Schema referencedSchema = this.getSchemaForNamespace(decl.getElementName().getNamespaceURI());
    
                            addImportIfRequired(currentSchema, referencedSchema, decl.getElementName().getNamespaceURI());
    
                            String prefix = this.getPrefixForNamespace(decl.getElementName().getNamespaceURI(), currentSchema.getNamespaceResolver());
                            
                            if(decl.getScopeClass() == GLOBAL.class){
                                if (prefix == null || prefix.equals("")) {
                                    element.setRef(localName);      
                                }else{
                                    element.setRef(prefix + ":" + localName);
                                }
                            }else{
                                element.setType(getTypeName(next, decl.getJavaType(), currentSchema));
                                element.setName(localName);
                            }
                            
                            if (next.getGenericType() != null) {
                                element.setMinOccurs(Occurs.ZERO);
                                element.setMaxOccurs(Occurs.UNBOUNDED);
                            }
                            parentCompositor.addElement(element);
                        } else {
                            // otherwise, add a choice of referenced elements.
                            Choice choice = new Choice();
                            if (next.getGenericType() != null) {
                                choice.setMaxOccurs(Occurs.UNBOUNDED);
                            }
                            for (ElementDeclaration elementDecl : referencedElements) {
                                Element element = new Element();
                                String localName = elementDecl.getElementName().getLocalPart();
                                Schema referencedSchema = this.getSchemaForNamespace(elementDecl.getElementName().getNamespaceURI());
    
                                addImportIfRequired(currentSchema, referencedSchema, elementDecl.getElementName().getNamespaceURI());
    
                                String prefix = this.getPrefixForNamespace(elementDecl.getElementName().getNamespaceURI(), currentSchema.getNamespaceResolver());
                                if(elementDecl.getScopeClass() == GLOBAL.class){
                                    if (prefix == null || prefix.equals("")) {
                                        element.setRef(localName);
                                    } else {
                                        element.setRef(prefix + ":" + localName);
                                    }
                                }else{
                                    element.setType(getTypeName(next, elementDecl.getJavaType(), referencedSchema));
                                    element.setName(localName);
                                }
                                choice.addElement(element);
                            }
                            if (parentCompositor instanceof Sequence) {
                                ((Sequence) parentCompositor).addChoice(choice);
                            } else if (parentCompositor instanceof Choice) {
                                ((Choice) parentCompositor).addChoice(choice);
                            }
                        }
                    } else if (!(ownerTypeInfo.getXmlValueProperty() != null && ownerTypeInfo.getXmlValueProperty() == next)) {
                        Element element = new Element();
                        // Set minOccurs based on the 'required' flag
                        if (!(parentCompositor instanceof All)) {
                            element.setMinOccurs(next.isRequired() ? Occurs.ONE : Occurs.ZERO);
                        }
                        // handle nillable
                        if (next.shouldSetNillable()) {
                            element.setNillable(true);
                        }
                        // handle defaultValue
                        if (next.isSetDefaultValue()) {
                            element.setDefaultValue(next.getDefaultValue());
                        }
                        // handle mime-type
                        if (next.getMimeType() != null) {
                            element.getAttributesMap().put(XMLConstants.EXPECTED_CONTENT_TYPES_QNAME, next.getMimeType());
                        }
    
                        QName elementName = next.getSchemaName();
                        JavaClass javaType = next.getActualType();                    
                        boolean isComplexType = false;
    
                        element.setName(elementName.getLocalPart());
    
                        String typeName = null;
                        if (next.isXmlId()) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":ID";
                        } else if (next.isXmlIdRef()) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":IDREF";
                        } else {
                            TypeInfo info = (TypeInfo) typeInfo.get(javaType.getQualifiedName());
                            if (info != null) {
                                isComplexType = info.isComplexType();
                                if (isComplexType) {
                                    typeName = info.getComplexType().getName();
                                } else if (info.getSimpleType() != null) {
                                    typeName = info.getSimpleType().getName();
                                } else {
                                    typeName = info.getSchemaTypeName();
                                }
    
                                if (typeName == null) {
                                    //need to add complex-type locally, or reference global element
                                    if (!info.hasRootElement()) {
                                        if (info.isComplexType()) {
                                            element.setComplexType(info.getComplexType());
                                        } else {
                                            element.setSimpleType(info.getSimpleType());
                                        }
                                    }
                                }
    
                                // check to see if we need to add an import
                                if (addImportIfRequired(currentSchema, info.getSchema(), info.getClassNamespace())) {
                                    String prefix = currentSchema.getNamespaceResolver().resolveNamespaceURI(info.getClassNamespace());
                                    if (prefix != null && !typeName.equals("")) {
                                        typeName = prefix + ":" + typeName;
                                    }
                                }
                            } else if (!next.isMap()) {
                                typeName = getTypeName(next, javaType, currentSchema);                         
                            }
    
                            // may need to qualify the type
                            if (typeName != null && !typeName.contains(":")) {
                                String prefix = getPrefixForNamespace(info.getSchema().getTargetNamespace(), currentSchema.getNamespaceResolver());
                                if (prefix != null) {
                                    typeName = prefix + ":" + typeName;
                                }
                            }
                        }
    
                        if (next.getGenericType() != null) {
                            if (next.isXmlList()) {
                                SimpleType localSimpleType = new SimpleType();
                                org.eclipse.persistence.internal.oxm.schema.model.List list = new org.eclipse.persistence.internal.oxm.schema.model.List();
                                list.setItemType(typeName);
                                localSimpleType.setList(list);
                                element.setSimpleType(localSimpleType);
                            } else {
                                element.setMaxOccurs(Occurs.UNBOUNDED);
                                element.setType(typeName);
                            }
                        } else if (next.isMap()) {
                                                    
                            ComplexType entryComplexType = new ComplexType();
                            Sequence entrySequence = new Sequence();
    
                            Element keyElement = new Element();
                            keyElement.setName(Property.DEFAULT_KEY_NAME);
                            keyElement.setMinOccurs(Occurs.ZERO);
    
                            JavaClass keyType = next.getKeyType();
                            JavaClass valueType = next.getValueType();
                                              
                            if(keyType == null){
                                keyType = helper.getJavaClass(Object.class);
                            }
                            
                            if(valueType == null){
                                valueType = helper.getJavaClass(Object.class);
                            }
                            
                            QName keySchemaType = getSchemaTypeFor(keyType);
                            if (keySchemaType != null) {
                                TypeInfo targetInfo = this.typeInfo.get(keyType.getQualifiedName());
                                if (targetInfo != null) {
                                    Schema keyElementSchema = this.getSchemaForNamespace(keySchemaType.getNamespaceURI());
                                    //add an import here
                                    addImportIfRequired(currentSchema, keyElementSchema, keySchemaType.getNamespaceURI());
                                }
                                String prefix;
                                if (keySchemaType.getNamespaceURI().equals(XMLConstants.SCHEMA_URL)) {
                                    prefix = XMLConstants.SCHEMA_PREFIX;
                                } else {
                                    prefix = getPrefixForNamespace(keySchemaType.getNamespaceURI(), currentSchema.getNamespaceResolver());
                                }
                                if (prefix != null && !prefix.equals("")) {
                                    typeName = prefix + ":" + keySchemaType.getLocalPart();
                                } else {
                                    typeName = keySchemaType.getLocalPart();
                                }
                                keyElement.setType(typeName);
                            }
    
                            entrySequence.addElement(keyElement);
    
                            Element valueElement = new Element();
                            valueElement.setName(Property.DEFAULT_VALUE_NAME);
                            valueElement.setMinOccurs(Occurs.ZERO);
                            QName valueSchemaType = getSchemaTypeFor(valueType);
                            if (valueSchemaType != null) {
                                TypeInfo targetInfo = this.typeInfo.get(valueType.getQualifiedName());
                                if (targetInfo != null) {
                                    Schema valueElementSchema = this.getSchemaForNamespace(valueSchemaType.getNamespaceURI());
                                    //add an import here
                                    addImportIfRequired(currentSchema, valueElementSchema, valueSchemaType.getNamespaceURI());
                                }
                                String prefix;
                                if (valueSchemaType.getNamespaceURI().equals(XMLConstants.SCHEMA_URL)) {
                                    prefix = XMLConstants.SCHEMA_PREFIX;
                                } else {
                                    prefix = getPrefixForNamespace(valueSchemaType.getNamespaceURI(), currentSchema.getNamespaceResolver());
                                }
                                if (prefix != null && !prefix.equals("")) {
                                    typeName = prefix + ":" + valueSchemaType.getLocalPart();
                                } else {
                                    typeName = valueSchemaType.getLocalPart();
                                }
                                valueElement.setType(typeName);
                            }
    
                            entrySequence.addElement(valueElement);
                            entryComplexType.setSequence(entrySequence);
    
                            JavaClass descriptorClass = helper.getJavaClass(ownerTypeInfo.getDescriptor().getJavaClassName());
                            JavaClass mapValueClass = helper.getJavaClass(MapValue.class);
    
                            if (mapValueClass.isAssignableFrom(descriptorClass)) {
                                element.setComplexType(entryComplexType);
                                element.setMaxOccurs(Occurs.UNBOUNDED);
                            } else {
                                ComplexType complexType = new ComplexType();
                                Sequence sequence = new Sequence();
                                complexType.setSequence(sequence);
    
                                Element entryElement = new Element();
                                entryElement.setName("entry");
                                entryElement.setMinOccurs(Occurs.ZERO);
                                entryElement.setMaxOccurs(Occurs.UNBOUNDED);
                                sequence.addElement(entryElement);
                                entryElement.setComplexType(entryComplexType);
    
                                element.setComplexType(complexType);
                            }
                        } else {
                            element.setType(typeName);
                        }
    
                        String lookupNamespace = currentSchema.getTargetNamespace();
                        if (lookupNamespace == null) {
                            lookupNamespace = "";
                        }
                        NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace);
                        boolean isElementFormQualified = false;
                        if (namespaceInfo != null) {
                            isElementFormQualified = namespaceInfo.isElementFormQualified();
                        }
                        if ((isElementFormQualified && !elementName.getNamespaceURI().equals(lookupNamespace))
                                    || (!isElementFormQualified && !elementName.getNamespaceURI().equals(""))){
                            Element reference = new Element();
                            reference.setMinOccurs(element.getMinOccurs());
                            reference.setMaxOccurs(element.getMaxOccurs());
                            Schema attributeSchema = this.getSchemaForNamespace(elementName.getNamespaceURI());
                            if (attributeSchema != null && attributeSchema.getTopLevelElements().get(element.getName()) == null) {
                                // reset min/max occurs as they aren't applicable for global elements
                                element.setMinOccurs(null);
                                element.setMaxOccurs(null);
                                //don't overwrite global elements. May have been defined by a type.
                                attributeSchema.getTopLevelElements().put(element.getName(), element);
                            }
    
                            //add an import here
                            addImportIfRequired(currentSchema, attributeSchema, elementName.getNamespaceURI());
    
                            String prefix = getPrefixForNamespace(elementName.getNamespaceURI(), currentSchema.getNamespaceResolver());
                            if (prefix == null) {
                                reference.setRef(element.getName());
                            } else {
                                reference.setRef(prefix + ":" + element.getName());
                            }
                            // make sure a ref doesn't already exist before adding this one
                            if (elementExistsInParticle(reference.getName(), reference.getRef(), parentCompositor) == null) {
                                parentCompositor.addElement(reference);
                            }
                        } else {
                            // for positional mappings we could have multiple elements with same name; check before adding
                            if (elementExistsInParticle(element.getName(), element.getRef(), parentCompositor) == null) {
                                if (next.isPositional()) {
                                    element.setMaxOccurs(Occurs.UNBOUNDED);
                                }
                                parentCompositor.addElement(element);
                            }
                        }
                    }
                }
            }
        }
    }

    public QName getSchemaTypeFor(JavaClass javaClass) {
        String className;
        if(javaClass.isArray()) {
            Class wrapperClass = arrayClassesToGeneratedClasses.get(javaClass.getName());
            if(null == wrapperClass) {
                className = javaClass.getQualifiedName();
            } else {
                className = wrapperClass.getName();
            }
        } else {
            className = javaClass.getQualifiedName();
        }
        // check user defined types first
        QName schemaType = (QName) userDefinedSchemaTypes.get(className);
        if (schemaType == null) {
            schemaType = (QName) helper.getXMLToJavaTypeMap().get(javaClass.getRawName());
        }
        if (schemaType == null) {
            TypeInfo targetInfo = this.typeInfo.get(className);
            if (targetInfo != null) {
                schemaType = new QName(targetInfo.getClassNamespace(), targetInfo.getSchemaTypeName());
            }
        }
        if (schemaType == null) {
            if (javaClass.getQualifiedName().equals("java.lang.Object")) {
                return XMLConstants.ANY_TYPE_QNAME;
            }
            return XMLConstants.ANY_SIMPLE_TYPE_QNAME;
        }
        return schemaType;
    }

    public void populateSchemaTypes() {
        Iterator<String> classNames = typeInfo.keySet().iterator();
        while (classNames.hasNext()) {
            String javaClassName = classNames.next();
            TypeInfo info = (TypeInfo) typeInfo.get(javaClassName);
            if (info.isComplexType()) {
                if (info.getSchema() != null) {
                    addToSchemaType(info, info.getNonTransientPropertiesInPropOrder(), info.getCompositor(), info.getComplexType(), info.getSchema());
                }
            }
        }
    }

    public String getSchemaTypeNameForClassName(String className) {
        String typeName = Introspector.decapitalize(className.substring(className.lastIndexOf('.') + 1));
        return typeName;
    }

    public ArrayList<String> getEnumerationFacetsFor(EnumTypeInfo info) {
        return (ArrayList<String>) info.getXmlEnumValues();
    }

    public Property getXmlValueFieldForSimpleContent(TypeInfo info) {
        ArrayList<Property> properties = info.getPropertyList();
        Property xmlValueProperty = info.getXmlValueProperty();
        boolean foundValue = false;
        boolean foundNonAttribute = false;
        Property valueField = null;

        for (Property prop : properties) {
            if (xmlValueProperty != null && xmlValueProperty == prop) {
                foundValue = true;
                valueField = prop;
            } else if (!prop.isAttribute() && !helper.isAnnotationPresent(prop.getElement(), XmlTransient.class) && !prop.isAnyAttribute()) {
                foundNonAttribute = true;
            }
        }
        if (foundValue && !foundNonAttribute) {
            return valueField;
        }
        return null;
    }

    public boolean isCollectionType(Property field) {
        JavaClass type = field.getType();
        return (helper.getJavaClass(java.util.Collection.class).isAssignableFrom(type) 
                || helper.getJavaClass(java.util.List.class).isAssignableFrom(type) 
                || helper.getJavaClass(java.util.Set.class).isAssignableFrom(type));
    }

    private Schema getSchemaForNamespace(String namespace) {
        if (schemaForNamespace == null) {
            schemaForNamespace = new HashMap<String, Schema>();
            allSchemas = new ArrayList<Schema>();
        }
        Schema schema = schemaForNamespace.get(namespace);
        if (schema == null) {

            NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(namespace);

            schema = new Schema();
            schema.setName("schema" + schemaCount + ".xsd");

            if (namespaceInfo != null) {
                if (namespaceInfo.getLocation() != null && !namespaceInfo.getLocation().equals("##generate")) {
                    return null;
                }
                java.util.Vector namespaces = namespaceInfo.getNamespaceResolver().getNamespaces();
                for (int i = 0; i < namespaces.size(); i++) {
                    Namespace nextNamespace = (Namespace) namespaces.get(i);
                    schema.getNamespaceResolver().put(nextNamespace.getPrefix(), nextNamespace.getNamespaceURI());
                }
            }
            schemaCount++;

            if (!namespace.equals("")) {
                schema.setTargetNamespace(namespace);
                String prefix = null;
                if (namespaceInfo != null) {
                    prefix = namespaceInfo.getNamespaceResolver().resolveNamespaceURI(namespace);
                }
                if (prefix == null) {
                    prefix = schema.getNamespaceResolver().generatePrefix();
                }
                schema.getNamespaceResolver().put(prefix, namespace);
            }

            if (namespaceInfo != null) {
                schema.setAttributeFormDefault(namespaceInfo.isAttributeFormQualified());
                schema.setElementFormDefault(namespaceInfo.isElementFormQualified());
            }
            schemaForNamespace.put(namespace, schema);
            allSchemas.add(schema);
        }
        return schema;
    }

    public Collection<Schema> getAllSchemas() {
        if (allSchemas == null) {
            allSchemas = new ArrayList<Schema>();
        }
        return allSchemas;
    }

    public NamespaceInfo getNamespaceInfoForNamespace(String namespace) {
        Collection<NamespaceInfo> namespaceInfo = packageToNamespaceMappings.values();
        for (NamespaceInfo info : namespaceInfo) {
            if (info.getNamespace().equals(namespace)) {
                return info;
            }
        }
        return null;
    }

    public String getPrefixForNamespace(String URI, org.eclipse.persistence.oxm.NamespaceResolver namespaceResolver) {
        Enumeration keys = namespaceResolver.getPrefixes();
        while (keys.hasMoreElements()) {
            String next = (String) keys.nextElement();
            String nextUri = namespaceResolver.resolveNamespacePrefix(next);
            if (nextUri.equals(URI)) {
                return next;
            }
        }
        return null;
    }

    /**
     * Attempt to resolve the given URI to a prefix.  If this is unsuccessful, one
     * will be generated and added to the resolver.
     * 
     * @param URI
     * @param schema
     * @return
     */
    public String getOrGeneratePrefixForNamespace(String URI, Schema schema) {
        String prefix = schema.getNamespaceResolver().resolveNamespaceURI(URI);
        if (prefix == null) {
            if (URI.equals(XMLConstants.SCHEMA_URL)) {
                prefix = schema.getNamespaceResolver().generatePrefix(XMLConstants.SCHEMA_PREFIX);
            } else if (URI.equals(XMLConstants.REF_URL)) {
                prefix = schema.getNamespaceResolver().generatePrefix(XMLConstants.REF_PREFIX);
            } else {
                prefix = schema.getNamespaceResolver().generatePrefix();
            }
            schema.getNamespaceResolver().put(prefix, URI);
        }
        return prefix;
    }

    public void addGlobalElements(HashMap<QName, ElementDeclaration> additionalElements) {
        for (Entry<QName, ElementDeclaration> entry : additionalElements.entrySet()) {
            QName next = entry.getKey();
            if(next != null) {
                ElementDeclaration nextElement = entry.getValue();
                if (nextElement.getScopeClass() == GLOBAL.class) {

                    String namespaceURI = next.getNamespaceURI();
                    Schema targetSchema = getSchemaForNamespace(namespaceURI);
                    if (targetSchema == null) {
                        break;
                    }

                    if (targetSchema.getTopLevelElements().get(next.getLocalPart()) == null) {
                        Element element = new Element();
                        element.setName(next.getLocalPart());

                        JavaClass javaClass = nextElement.getJavaType();

                        //First check for built in type
                        QName schemaType = (QName) helper.getXMLToJavaTypeMap().get(javaClass.getRawName());
                        if (schemaType != null) {
                            element.setType(XMLConstants.SCHEMA_PREFIX + ":" + schemaType.getLocalPart());
                        } else if (areEquals(javaClass, JAVAX_ACTIVATION_DATAHANDLER) || areEquals(javaClass, byte[].class) || areEquals(javaClass, Byte[].class) || areEquals(javaClass, Image.class) || areEquals(javaClass, Source.class) || areEquals(javaClass, JAVAX_MAIL_INTERNET_MIMEMULTIPART)) {
                            schemaType = XMLConstants.BASE_64_BINARY_QNAME;
                            if(nextElement.getTypeMappingInfo() != null) {
                                if(nextElement.isXmlAttachmentRef()) {
                                    schemaType = XMLConstants.SWA_REF_QNAME;
                                }
                                if (nextElement.getXmlMimeType() != null) {
                                    element.getAttributesMap().put(XMLConstants.EXPECTED_CONTENT_TYPES_QNAME, nextElement.getXmlMimeType());
                                }
                            }
                            String prefix = getOrGeneratePrefixForNamespace(schemaType.getNamespaceURI(), targetSchema);
                            element.setType(prefix + ":" + schemaType.getLocalPart());
                        } else {
                            TypeInfo type = (TypeInfo) this.typeInfo.get(javaClass.getQualifiedName());
                            if (type != null) {
                                String typeName = null;
                                if (type.isComplexType()) {
                                    typeName = type.getComplexType().getName();
                                } else {
                                    typeName = type.getSimpleType().getName();
                                }
                                //  check namespace of schemaType
                                if (type.getClassNamespace().equals(namespaceURI)) {
                                    //no need to prefix here
                                    String prefix = targetSchema.getNamespaceResolver().resolveNamespaceURI(namespaceURI);
                                    if(prefix != null && !(prefix.equals(""))) {
                                        element.setType(prefix + ":" + typeName);
                                    } else {
                                        element.setType(typeName);
                                    }
                                } else {
                                    Schema complexTypeSchema = getSchemaForNamespace(type.getClassNamespace());

                                    String complexTypeSchemaNS = type.getClassNamespace();
                                    if (complexTypeSchemaNS == null) {
                                        complexTypeSchemaNS = "";
                                    }
                                    addImportIfRequired(targetSchema, complexTypeSchema, type.getClassNamespace());

                                    String prefix = targetSchema.getNamespaceResolver().resolveNamespaceURI(complexTypeSchemaNS);
                                    if (prefix != null) {
                                        element.setType(prefix + ":" + typeName);
                                    } else {
                                        element.setType(typeName);
                                    }
                                }
                            }

                        }
                        if (nextElement.getSubstitutionHead() != null) {
                            String subLocal = nextElement.getSubstitutionHead().getLocalPart();
                            String subNamespace = nextElement.getSubstitutionHead().getNamespaceURI();
                            String prefix = getPrefixForNamespace(subNamespace, targetSchema.getNamespaceResolver());
                            if (prefix == null || prefix.equals("")) {
                                element.setSubstitutionGroup(subLocal);
                            } else {
                                element.setSubstitutionGroup(prefix + ":" + subLocal);
                            }
                        }
                        targetSchema.addTopLevelElement(element);
                        SchemaTypeInfo info = this.schemaTypeInfo.get(javaClass.getQualifiedName());
                        if (info == null) {
                            // probably for a simple type, not generated
                            info = new SchemaTypeInfo();
                            info.setSchemaTypeName(schemaType);
                            schemaTypeInfo.put(javaClass.getQualifiedName(), info);
                        }
                        info.getGlobalElementDeclarations().add(next);
                    }
                }
            }
        }
    }

    public HashMap<String, SchemaTypeInfo> getSchemaTypeInfo() {
        return this.schemaTypeInfo;
    }

    private boolean importExists(Schema schema, String schemaName) {
        java.util.List imports = schema.getImports();
        for (int i = 0; i < imports.size(); i++) {
            Import nextImport = (Import) imports.get(i);
            if (nextImport.getSchemaLocation() != null && nextImport.getSchemaLocation().equals(schemaName)) {
                return true;
            }
        }
        return false;
    }

    private boolean addImportIfRequired(Schema sourceSchema, Schema importSchema, String importNamespace) {
        if (importSchema != sourceSchema) {
            String schemaName = null;
            if (importSchema != null) {
                schemaName = importSchema.getName();
            } else if (importNamespace != null) {
                NamespaceInfo nInfo = getNamespaceInfoForNamespace(importNamespace);
                schemaName = nInfo.getLocation();
            }

            if (schemaName != null && !importExists(sourceSchema, schemaName)) {
                Import schemaImport = new Import();
                schemaImport.setSchemaLocation(schemaName);
                if (importNamespace != null && !importNamespace.equals("")) {
                    schemaImport.setNamespace(importNamespace);
                }
                sourceSchema.getImports().add(schemaImport);
                if (schemaImport.getNamespace() != null) {
                    String prefix = sourceSchema.getNamespaceResolver().resolveNamespaceURI(importNamespace);
                    //Don't need to generate prefix for default namespace
                    if (prefix == null && !importNamespace.equals("")) {
                        sourceSchema.getNamespaceResolver().put(sourceSchema.getNamespaceResolver().generatePrefix(), importNamespace);
                    }
                }
                return true;
            }
        }
        return false;
    }
    
    /**
     * Compares a JavaModel JavaClass to a Class.  Equality is based on
     * the raw name of the JavaClass compared to the canonical
     * name of the Class.
     * 
     * @param src
     * @param tgt
     * @return
     */
    protected boolean areEquals(JavaClass src, String tgtCanonicalName) {
        if (src == null || tgtCanonicalName == null) {
            return false;
        }
        return src.getRawName().equals(tgtCanonicalName);
    }  
    
    /**
     * Compares a JavaModel JavaClass to a Class.  Equality is based on
     * the raw name of the JavaClass compared to the canonical
     * name of the Class.
     * 
     * @param src
     * @param tgt
     * @return
     */
    protected boolean areEquals(JavaClass src, Class tgt) {
        if (src == null || tgt == null) {
            return false;
        }
        return src.getRawName().equals(tgt.getCanonicalName());
    }    
    
    private String getTypeName(Property next, JavaClass javaType, Schema theSchema){ 
        QName schemaType = next.getSchemaType();
        if (schemaType == null) {
            schemaType = getSchemaTypeFor(javaType);
        }
        if (schemaType != null) {
            if (schemaType.getNamespaceURI() == null) {
                return schemaType.getLocalPart();
            } else {
                String prefix = getOrGeneratePrefixForNamespace(schemaType.getNamespaceURI(), theSchema);
                return prefix + ":" + schemaType.getLocalPart();
            }
        } else {
            return XMLConstants.SCHEMA_PREFIX + ":anySimpleType";
        }
    }

    /**
     * This method will build element/complexType/typedefparticle components for a given xml-path, 
     * and return an XmlPathResult instance containg the sequence that the target should be added
     * to, as well as the current schema - which could be different than the working schema used
     * before calling this method in the case of a prefixed path element from a different namespace.
     * Regarding the path 'target', if the xml-path was "contact-info/address/street", "street" 
     * would be the target.  In this case the sequence containing the "address" element would be 
     * set in the XmlPathResult to be returned.
     * 
     * The exception case is an 'any', where we want to process the last path element before 
     * returning - this is necessary due to the fact that an Any will be added to the sequence 
     * in place of the last path element by the calling method. 
     * 
     * @param frag
     * @param xpr
     * @param isAny
     * @param isChoice
     * @return
     */
    protected XmlPathResult buildSchemaComponentsForXPath(XPathFragment frag, XmlPathResult xpr, boolean isAny, boolean isChoice, Property next) {
        TypeDefParticle currentParticle = xpr.particle;
        Schema workingSchema = xpr.schema;
        
        // each nested choice on a collection will be unbounded
        boolean isUnbounded = (currentParticle.getMaxOccurs() != null && currentParticle.getMaxOccurs()==Occurs.UNBOUNDED);
        
        // don't process the last frag; that will be handled by the calling method if necessary
        // note that we may need to process the last frag if it has a namespace or is an 'any'
        boolean lastFrag = (frag.getNextFragment() == null || frag.getNextFragment().nameIsText());
        
        // if the element is already in the sequence we don't want the calling method to add a second one
        if (lastFrag && (elementExistsInParticle(frag.getLocalName(), frag.getShortName(), currentParticle) != null)) {
            xpr.particle = null;
            return xpr;
        }
        
        // if the current element exists, use it; otherwise create a new one
        Element currentElement = elementExistsInParticle(frag.getLocalName(), frag.getShortName(), currentParticle);
        boolean currentElementExists = (currentElement != null);
        if (!currentElementExists) {
            currentElement = new Element();
            // don't set the element name yet, as it may end up being a ref
            ComplexType cType = new ComplexType();
            TypeDefParticle particle = null;
            if (isChoice) {
                particle = new Choice();
                if (isUnbounded) {
                    particle.setMaxOccurs(Occurs.UNBOUNDED);
                }
            } else {
                particle = new Sequence();
            }
            cType.setTypeDefParticle(particle);
            currentElement.setComplexType(cType);
        }
        // may need to create a ref, depending on the namespace
        Element globalElement = null;

        String fragUri = frag.getNamespaceURI();
        if (fragUri != null) {
            Schema fragSchema = getSchemaForNamespace(fragUri);
            String targetNS = workingSchema.getTargetNamespace();
            
            // handle Attribute case
            if (frag.isAttribute()) {
                if ((fragSchema.isAttributeFormDefault() && !fragUri.equals(targetNS)) || (!fragSchema.isAttributeFormDefault() && fragUri.length() > 0)) {
                    // must generate a global attribute and create a reference to it
                    // if the global attribute exists, use it; otherwise create a new one
                    Attribute globalAttribute = null;
                    globalAttribute = (Attribute) fragSchema.getTopLevelAttributes().get(frag.getLocalName());
                    if (globalAttribute == null) {
                        globalAttribute = createGlobalAttribute(frag, workingSchema, fragSchema, next);
                    }
                    // add the attribute ref to the current element
                    String attributeRefName;
                    if (fragUri.equals(targetNS)) {
                        String prefix = fragSchema.getNamespaceResolver().resolveNamespaceURI(fragUri);
                        attributeRefName = prefix + ":" + frag.getLocalName();
                    } else {
                        attributeRefName = frag.getShortName();
                    }
                    // 
                    if (currentParticle.getOwner() instanceof ComplexType) {
                        createRefAttribute(attributeRefName, (ComplexType)currentParticle.getOwner());
                    }

                    // set the frag's schema as it may be different than the current schema
                    xpr.schema = fragSchema;
                    // ref case - indicate to the calling method that there's nothing to do
                    xpr.particle = null;
                }
                // since we are dealing with an attribute, we are on the last fragment; return
                return xpr;
            }
            
            // here we are dealing with an Element
            if ((fragSchema.isElementFormDefault() && !fragUri.equals(targetNS)) || (!fragSchema.isElementFormDefault() && fragUri.length() > 0)) {
                // must generate a global element and create a reference to it
                // if the global element exists, use it; otherwise create a new one
                globalElement = (Element) fragSchema.getTopLevelElements().get(frag.getLocalName());
                if (globalElement == null) {
                    globalElement = createGlobalElement(frag, workingSchema, fragSchema, isChoice, isUnbounded); 
                }
                // if the current element doesn't exist set a ref and add it to the sequence
                if (!currentElementExists) {
                    currentElement = createRefElement(frag.getShortName(), currentParticle);
                    currentElementExists = true;
                }
                // set the frag's schema as it may be different than the current schema
                xpr.schema = fragSchema;
                // at this point, if we are dealing with the last fragment we will need to return
                if (lastFrag) {
                    // since we processed the last frag, return null so the calling method doesn't
                    // add a second one...unless we're dealing with an 'any'
                    if (isAny) {
                        // set the particle that the 'any' will be added to by the calling method
                        xpr.particle = globalElement.getComplexType().getTypeDefParticle();
                        return xpr;
                    }
                    // ref case - indicate to the calling method that there's nothing to do
                    xpr.particle = null;
                    return xpr;
                }
                // make the global element current 
                currentElement = globalElement;
            }
        }
        
        if (!lastFrag || (lastFrag && isAny)) {
            // 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());
                currentElement.setMinOccurs(Occurs.ZERO);
                currentParticle.addElement(currentElement);
            }
            // set the correct particle to use/return
            xpr.particle = currentElement.getComplexType().getTypeDefParticle();
        }
        
        // if we're on the last fragment, we're done
        if (lastFrag) {
            return xpr;
        }

        // call back into this method to process the next path element
        return buildSchemaComponentsForXPath(frag.getNextFragment(), xpr, isAny, isChoice, next);
    }
    
    /**
     * Convenience method for determining if an element already exists in a given
     * typedefparticle.  If an element exists whose ref is equal to 'refString'
     * or its name is equal to 'elementName', it is returned.  Null otherwise.
     * 
     * @param elementName
     * @param refString
     * @param particle
     * @return
     */
    protected Element elementExistsInParticle(String elementName, String refString, TypeDefParticle particle) {
        if (particle.getElements() == null || particle.getElements().size() == 0) { 
            return null;
        }
        java.util.List existingElements = particle.getElements();
        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;
    }
    
    /**
     * Create a global attribute.  An import is added if necessary.  This method
     * will typically be called when processing an XPath and a prefixed path
     * element is encountered tha requires an attribute ref.
     * 
     * @param frag
     * @param workingSchema
     * @param fragSchema
     * @param next
     * @return
     */
    public Attribute createGlobalAttribute(XPathFragment frag, Schema workingSchema, Schema fragSchema, Property next) {
        Attribute gAttribute = new Attribute();
        gAttribute.setName(frag.getLocalName());
        
        JavaClass javaType = next.getActualType();
        String typeName = getTypeName(next, javaType, fragSchema);
        // may need to qualify the type
        if (typeName != null && !typeName.contains(":")) {
            String prefix = getPrefixForNamespace(fragSchema.getTargetNamespace(), fragSchema.getNamespaceResolver());
            if (prefix != null) {
                typeName = prefix + ":" + typeName;
            }
        }
        gAttribute.setType(typeName);
        
        fragSchema.getTopLevelAttributes().put(gAttribute.getName(), gAttribute);
        addImportIfRequired(workingSchema, fragSchema, frag.getNamespaceURI());
        return gAttribute;
    }

    /**
     * Create a global element.  An import is added if necessary.  This method
     * will typically be called when processing an XPath and a prefixed path
     * element is encountered the requires an element ref.
     * 
     * @param frag
     * @param workingSchema
     * @param fragSchema
     * @param isChoice
     * @param isUnbounded
     * @return
     */
    public Element createGlobalElement(XPathFragment frag, Schema workingSchema, Schema fragSchema, boolean isChoice, boolean isUnbounded) {
        Element gElement = new Element();
        gElement.setName(frag.getLocalName());
        ComplexType gCType = new ComplexType();
        TypeDefParticle particle;
        if (isChoice) {
            particle = new Choice();
            if (isUnbounded) {
                particle.setMaxOccurs(Occurs.UNBOUNDED);
            }
        } else {
            particle = new Sequence();
        }
        gCType.setTypeDefParticle(particle);
        gElement.setComplexType(gCType);
        fragSchema.addTopLevelElement(gElement);
        addImportIfRequired(workingSchema, fragSchema, frag.getNamespaceURI());
        return gElement;
    }

    /**
     * Create an element reference and add it to a given particle. This 
     * method will typically be called when processing an XPath and a 
     * prefixed path element is encountered that requires an element ref.
     * 
     * @param elementRefName
     * @param particle
     * @return
     */
    public Element createRefElement(String elementRefName, TypeDefParticle particle) {
        Element refElement = new Element();
        // ref won't have a complex type                    
        refElement.setComplexType(null);
        refElement.setMinOccurs(Occurs.ZERO);
        refElement.setMaxOccurs(Occurs.ONE);
        refElement.setRef(elementRefName);
        particle.addElement(refElement);
        return refElement;
    }

    /**
     * Create an attribute reference and add it to a given complex type.
     * This method will typically be called when processing an XPath 
     * and a prefixed path element is encountered that requires an 
     * attribute ref.
     * 
     * @param attributeRefName
     * @param owningComplexType
     * @return
     */
    public Attribute createRefAttribute(String attributeRefName, ComplexType owningComplexType) {
        Attribute refAttribute = new Attribute();
        refAttribute.setRef(attributeRefName);
        if (owningComplexType.getSimpleContent() != null) {
            owningComplexType.getSimpleContent().getExtension().getOrderedAttributes().add(refAttribute);
        } else {
            owningComplexType.getOrderedAttributes().add(refAttribute);
        }
        return refAttribute;
    }
    
    /**
     * This class will typically be used when processing an xml-path.  It will hold the 
     * TypeDefParticle (all, sequence, choice) and schema that are to be used by the 
     * method that is processing the property that has the xml-path set on it.
     *
     */
    class XmlPathResult {
        TypeDefParticle particle;
        Schema schema;
        
        XmlPathResult(TypeDefParticle particle, Schema schema) {
            this.particle = particle;
            this.schema = schema;
        }
    }
}