/*
 * Copyright (c) 1998, 2024 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:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.jaxb.compiler;

import java.awt.Image;
import java.beans.Introspector;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import org.eclipse.persistence.exceptions.BeanValidationException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.jaxb.many.MapValue;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.schema.model.All;
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.SimpleComponent;
import org.eclipse.persistence.internal.oxm.schema.model.SimpleContent;
import org.eclipse.persistence.internal.oxm.schema.model.SimpleType;
import org.eclipse.persistence.internal.oxm.schema.model.TypeDefParticle;
import org.eclipse.persistence.internal.oxm.schema.model.TypeDefParticleOwner;
import org.eclipse.persistence.jaxb.MOXySystemProperties;
import org.eclipse.persistence.jaxb.compiler.builder.TransformerPropertyBuilder;
import org.eclipse.persistence.jaxb.compiler.facets.DecimalMaxFacet;
import org.eclipse.persistence.jaxb.compiler.facets.DecimalMinFacet;
import org.eclipse.persistence.jaxb.compiler.facets.DigitsFacet;
import org.eclipse.persistence.jaxb.compiler.facets.Facet;
import org.eclipse.persistence.jaxb.compiler.facets.FacetVisitor;
import org.eclipse.persistence.jaxb.compiler.facets.MaxFacet;
import org.eclipse.persistence.jaxb.compiler.facets.MinFacet;
import org.eclipse.persistence.jaxb.compiler.facets.PatternFacet;
import org.eclipse.persistence.jaxb.compiler.facets.PatternListFacet;
import org.eclipse.persistence.jaxb.compiler.facets.SizeFacet;
import org.eclipse.persistence.jaxb.javamodel.Helper;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.javamodel.JavaClassCompareByNamespace;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElementWrapper;
import org.eclipse.persistence.jaxb.xmlmodel.XmlJoinNodes.XmlJoinNode;
import org.eclipse.persistence.jaxb.xmlmodel.XmlVirtualAccessMethodsSchema;
import org.eclipse.persistence.oxm.NamespaceResolver;
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 Map<String, Schema> schemaForNamespace;
    private List<Schema> allSchemas;
    private int schemaCount;
    private Helper helper;
    private Map<String, TypeInfo> typeInfo;
    private Map<String, PackageInfo> packageToPackageInfoMappings;
    private Map<String, SchemaTypeInfo> schemaTypeInfo;
    private Map<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";
    private static final String SWA_REF_IMPORT = "http://ws-i.org/profiles/basic/1.1/swaref.xsd";

    private static final String COLON = ":";
    private static final String ATT = "@";
    private static final String EMPTY_STRING = "";
    private static final String DOT = ".";
    private static final String SKIP = "skip";
    private static final String ENTRY = "entry";
    private static final String GENERATE = "##generate";
    private static final String SCHEMA = "schema";
    private static final String SCHEMA_EXT = ".xsd";
    private static final String OBJECT_CLASSNAME = "java.lang.Object";
    private static final String ID = "ID";
    private static final String IDREF = "IDREF";
    private static final Character DOT_CHAR = '.';
    private static final Character SLASH = '/';
    private static final Character SLASHES = '\\';

    private SchemaOutputResolver outputResolver;
    private boolean facets;

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

    public void generateSchema(List<JavaClass> typeInfoClasses, Map<String, TypeInfo> typeInfo, Map<String, QName> userDefinedSchemaTypes, Map<String, PackageInfo> packageToPackageInfoMappings, Map<QName, ElementDeclaration> additionalGlobalElements, Map<String, Class> arrayClassesToGeneratedClasses, SchemaOutputResolver outputResolver) {
        this.outputResolver = outputResolver;
        generateSchema(typeInfoClasses, typeInfo, userDefinedSchemaTypes, packageToPackageInfoMappings, additionalGlobalElements, arrayClassesToGeneratedClasses);
    }

    public void generateSchema(List<JavaClass> typeInfoClasses, Map<String, TypeInfo> typeInfo, Map<String, QName> userDefinedSchemaTypes, Map<String, PackageInfo> packageToPackageInfoMappings, Map<QName, ElementDeclaration> additionalGlobalElements, Map<String, Class> arrayClassesToGeneratedClasses) {
        this.typeInfo = typeInfo;
        this.userDefinedSchemaTypes = userDefinedSchemaTypes;
        this.packageToPackageInfoMappings = packageToPackageInfoMappings;
        this.schemaCount = 1;
        this.schemaTypeInfo = new HashMap<String, SchemaTypeInfo>(typeInfo.size());
        this.arrayClassesToGeneratedClasses = arrayClassesToGeneratedClasses;

        //sort input classes before schema name (like schema1.xsd, schema2.xsd....) is generated and assigned
        if (MOXySystemProperties.moxySchemaGeneratorSort) {
            typeInfoClasses.sort(new JavaClassCompareByNamespace(typeInfo));
        }
        for (JavaClass javaClass : typeInfoClasses) {
            addSchemaComponents(javaClass);
        }
        populateSchemaTypes();
        if (additionalGlobalElements != null) {
            addGlobalElements(additionalGlobalElements);
        }
    }

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

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

        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(XMLProcessor.DEFAULT) || elementName.equals(EMPTY_STRING)) {
                try{
                    elementName = info.getXmlNameTransformer().transformRootElementName(myClassName);
                }catch (Exception ex){
                    throw org.eclipse.persistence.exceptions.JAXBException.exceptionDuringNameTransformation(myClassName, info.getXmlNameTransformer().getClass().getName(), ex);
                }
            }
            rootElement.setName(elementName);
            String rootNamespace = xmlRE.getNamespace();
            if (rootNamespace.equals(XMLProcessor.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(EMPTY_STRING)) {
                pfx = getOrGeneratePrefixForNamespace(info.getClassNamespace(), rootSchema);
                pfx += COLON;
            }
        }



        if (CompilerHelper.isSimpleType(info)){

            SimpleType type = new SimpleType();
            //simple type case, we just need the name and namespace info
            if (typeName.equals(EMPTY_STRING)) {
                //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));

                String prefix = null;
                if (restrictionType.getNamespaceURI() != null && !EMPTY_STRING.equals(restrictionType.getNamespaceURI())) {
                    if (javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(restrictionType.getNamespaceURI())) {
                        prefix = Constants.SCHEMA_PREFIX;
                    } else {
                        prefix = getPrefixForNamespace(schema, restrictionType.getNamespaceURI());
                    }
                }
                String extensionTypeName = restrictionType.getLocalPart();
                if (prefix != null) {
                    extensionTypeName = prefix + COLON + extensionTypeName;
                }
                restriction.setBaseType(extensionTypeName);

                type.setRestriction(restriction);
            } else {
                valueField= info.getXmlValueProperty();
                JavaClass javaType = valueField.getActualType();
                QName baseType = getSchemaTypeFor(javaType);
                String prefix = null;
                if (baseType.getNamespaceURI() != null && !baseType.getNamespaceURI().equals(EMPTY_STRING)) {
                    if (baseType.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                        prefix = Constants.SCHEMA_PREFIX;
                    } else {
                        prefix = getPrefixForNamespace(schema, baseType.getNamespaceURI());
                    }
                }
                String baseTypeName = baseType.getLocalPart();
                if (prefix != null) {
                    baseTypeName = prefix + COLON + baseTypeName;
                }
                if (valueField.isXmlList() || (valueField.getGenericType() != null)) {
                    //generate a list instead of a restriction
                    org.eclipse.persistence.internal.oxm.schema.model.List list = new org.eclipse.persistence.internal.oxm.schema.model.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()); // TODO: This assignment seems like a bug, probably this should be "baseTypeName" ?
                    }
                    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 (EMPTY_STRING.equals(typeName)) {
                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(EMPTY_STRING)) {
                if (extensionType.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                    prefix = Constants.SCHEMA_PREFIX;
                } else {
                    prefix = getPrefixForNamespace(schema, extensionType.getNamespaceURI());
                }
            }
            String extensionTypeName = extensionType.getLocalPart();
            if (prefix != null) {
                extensionTypeName = prefix + COLON + extensionTypeName;
            }
            Extension extension = new Extension();
            extension.setBaseType(extensionTypeName);
            content.setExtension(extension);
            type.setSimpleContent(content);
            info.setComplexType(type);
        } else {
            ComplexType type = createComplexTypeForClass(myClass, info);
            TypeDefParticle compositor = null;
            if(type.getComplexContent() != null && type.getComplexContent().getExtension() != null) {
                compositor = type.getComplexContent().getExtension().getTypeDefParticle();
            } else {
                compositor = type.getTypeDefParticle();
            }
            if (EMPTY_STRING.equals(typeName)) {
                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);
            }
        }
    }

    private ComplexType createComplexTypeForClass(JavaClass myClass, TypeInfo info) {

        Schema schema = getSchemaForNamespace(info.getClassNamespace());

        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(schema, parentTypeInfo.getClassNamespace());
                if (parentPrefix != null) {
                    extension.setBaseType(parentPrefix + COLON + parentTypeInfo.getSchemaTypeName());
                } else {
                    extension.setBaseType(parentTypeInfo.getSchemaTypeName());
                }

                if(parentTypeInfo.getXmlValueProperty() != null){
                    SimpleContent content = new SimpleContent();
                    content.setExtension(extension);
                    type.setSimpleContent(content);
                    return type;
                }else{
                    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);
            }
        }
        return type;
    }
    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);
            return;
        }

        boolean extAnyAdded = false;

        // generate schema components for each property
        for (Property next : properties) {
            if (next == null) { continue; }
            Schema currentSchema = workingSchema;
            TypeDefParticle parentCompositor = compositor;
            boolean isChoice = (parentCompositor instanceof Choice);
            ComplexType parentType = type;
            // ignore transient and inverse reference/non-writeable properties
            if(!next.isTransient() && !(next.isInverseReference() && !next.isWriteableInverseReference())){
                // handle xml extensions
                if (next.isVirtual()) {
                    boolean extSchemaAny = false;
                    if (ownerTypeInfo.getXmlVirtualAccessMethods().getSchema() != null) {
                        extSchemaAny = ownerTypeInfo.getXmlVirtualAccessMethods().getSchema().equals(XmlVirtualAccessMethodsSchema.ANY);
                    }
                    if (extSchemaAny && !next.isAttribute()) {
                        if (!extAnyAdded) {
                            addAnyToSchema(next, compositor, true, Constants.ANY_NAMESPACE_ANY);
                            extAnyAdded = true;
                            continue;
                        } else {
                            // any already added
                            continue;
                        }
                    } else {
                        // proceed, adding the schema element as usual
                    }
                }
                // handle transformers
                if (next.isSetXmlTransformation() && next.getXmlTransformation().isSetXmlWriteTransformers()) {
                    addTransformerToSchema(next, ownerTypeInfo, compositor, type, workingSchema);
                    continue;
                }
                // handle XmlJoinNodes
                if (next.isSetXmlJoinNodes()) {
                    addXmlJoinNodesToSchema(next, parentCompositor, currentSchema, parentType);
                    continue;
                }
                // deal with xml-path case
                if (next.getXmlPath() != null) {
                    // create schema components based on the XmlPath
                    AddToSchemaResult xpr = addXPathToSchema(next, parentCompositor, currentSchema, isChoice, type);
                    // if the returned object or schema component is null there is nothing to do
                    if (xpr == null || ((parentCompositor = xpr.particle) == null && xpr.simpleContentType == null)) {
                        continue;
                    }
                    // now process the property as per usual, adding to the created schema component
                    if(xpr.schema == null) {
                        //if there's no schema, this may be a ref to an attribute in an ungenerated schema
                        //no need to generate the attribute in the target schema
                        continue;
                    }
                    currentSchema = xpr.schema;
                    if(parentCompositor == null) {
                        parentType = xpr.simpleContentType;
                    } else if (parentCompositor.getOwner() instanceof ComplexType) {
                        parentType = ((ComplexType)parentCompositor.getOwner());
                    }
                    // deal with the XmlElementWrapper case
                } else if (!isChoice && !next.isMap() && next.isSetXmlElementWrapper()) {
                    AddToSchemaResult asr = addXmlElementWrapperToSchema(next, currentSchema, compositor);
                    // if returned object is null there is nothing to do
                    if (asr == null) {
                        continue;
                    }
                    // the returned object contains ComplexType and TypeDefParticles to use
                    parentType = asr.type;
                    parentCompositor = asr.particle;
                }
                // handle mixed content
                if (next.isMixedContent()) {
                    parentType.setMixed(true);
                }
                // handle attribute
                if (next.isAttribute() && !next.isAnyAttribute()) {
                    addAttributeToSchema(buildAttribute(next, currentSchema), next.getSchemaName(), currentSchema, parentType);
                    // handle any attribute
                } else if (next.isAnyAttribute()) {
                    addAnyAttributeToSchema(parentType);
                    // handle choice
                } else if (next.isChoice()) {
                    addChoiceToSchema(next, ownerTypeInfo, parentType, parentCompositor, currentSchema);
                    // handle reference
                } else if (next.isReference()) {
                    addReferenceToSchema(next, currentSchema, parentCompositor);
                    // handle any
                } else if (next.isAny() || next.getVariableAttributeName() !=null) {
                    addAnyToSchema(next, parentCompositor);
                    // add an element
                } else if (!(ownerTypeInfo.getXmlValueProperty() != null && ownerTypeInfo.getXmlValueProperty() == next)) {
                    Element element = buildElement(next, parentCompositor instanceof All, currentSchema, ownerTypeInfo);
                    addElementToSchema(element, next.getSchemaName().getNamespaceURI(), next.isPositional(), parentCompositor, currentSchema, ownerTypeInfo.getJavaClass().getPackageName());
                }
            }
        }
    }

    /**
     * Return the schema type (as QName) based on a given JavaClass.
     *
     * @param javaClass
     * @return
     */
    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 = 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(OBJECT_CLASSNAME)) {
                return Constants.ANY_TYPE_QNAME;
            }
            return Constants.ANY_SIMPLE_TYPE_QNAME;
        }
        return schemaType;
    }

    public void populateSchemaTypes() {
        for (TypeInfo info : typeInfo.values()) {
            if (info.isComplexType()) {
                if (info.getSchema() != null) {
                    List<Property> props = info.getNonTransientPropertiesInPropOrder();
                    // handle class indicator field name
                    if (info.isSetXmlDiscriminatorNode()) {
                        String xpath = info.getXmlDiscriminatorNode();
                        String pname = XMLProcessor.getNameFromXPath(xpath, EMPTY_STRING, true);
                        if (!pname.equals(EMPTY_STRING)) {
                            // since there is no property for the indicator field name, we'll need to make one
                            Property prop = new Property(helper);
                            prop.setPropertyName(pname);
                            prop.setXmlPath(xpath);
                            prop.setSchemaName(new QName(pname));
                            prop.setType(helper.getJavaClass(String.class));
                            prop.setIsAttribute(true);
                            props.add(prop);
                        }
                    }
                    addToSchemaType(info, props, info.getCompositor(), info.getComplexType(), info.getSchema());
                    if (info.hasPredicateProperties()) {
                        addToSchemaType(info, info.getPredicateProperties(), info.getCompositor(), info.getComplexType(), info.getSchema());
                    }
                }
            }
        }
    }

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

    public ArrayList<Object> getEnumerationFacetsFor(EnumTypeInfo info) {
        return (ArrayList<Object>) 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() && !prop.isTransient() && !prop.isAnyAttribute()) {
                foundNonAttribute = true;
            }
        }
        if (foundValue && !foundNonAttribute) {
            return valueField;
        }
        return null;
    }

    /**
     * Indicates if a given Property is a collection type.
     *
     * @param field
     * @return
     */
    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) {
        return getSchemaForNamespace(namespace, null);
    }

    /**
     * Return the Schema for a given namespace.  If no schema exists for the
     * given namespace, one will be created.
     *
     * @param namespace
     * @return
     */
    private Schema getSchemaForNamespace(String namespace, String packageName) {
        if (schemaForNamespace == null) {
            schemaForNamespace = new HashMap<String, Schema>();
            allSchemas = new ArrayList<Schema>();
        }
        Schema schema = schemaForNamespace.get(namespace);
        if (schema == null && !javax.xml.XMLConstants.XML_NS_URI.equals(namespace)) {
            schema = new Schema();
            String schemaName = SCHEMA + schemaCount + SCHEMA_EXT;

            NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(namespace, packageName);
            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());
                }
            }

            if (outputResolver != null) {
                try {
                    Result res = outputResolver.createOutput(namespace, schemaName);
                    // if the resolver returns null, schema generation for this namespace URI will be skipped
                    if (res == null) {
                        return null;
                    }
                    schema.setResult(res);
                    if (res.getSystemId() != null) {
                        schemaName = res.getSystemId();
                        // may use schema name to create a URI, which expects '/' as file separator
                        schemaName = schemaName.replace(SLASHES, SLASH);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            schema.setName(schemaName);
            schemaCount++;

            if (!namespace.equals(EMPTY_STRING)) {
                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) {

        return getNamespaceInfoForNamespace(namespace, null);
    }

    public NamespaceInfo getNamespaceInfoForNamespace(String namespace, String packageName) {

        if (null != packageName) {
            if (packageToPackageInfoMappings.containsKey(packageName)) {
                PackageInfo packageInfo = packageToPackageInfoMappings.get(packageName);
                if (packageInfo.getNamespace().equals(namespace)) {
                    return packageInfo.getNamespaceInfo();
                }
            }
        }

        Collection<PackageInfo> packageInfo = packageToPackageInfoMappings.values();
        for (PackageInfo info : packageInfo) {
            if (info.getNamespace().equals(namespace)) {
                return info.getNamespaceInfo();
            }
        }
        return null;
    }

    public String getPrefixForNamespace(Schema schema, String URI) {
        //add Import if necessary
        Schema referencedSchema = this.getSchemaForNamespace(URI);
        addImportIfRequired(schema, referencedSchema, URI);

        NamespaceResolver namespaceResolver = schema.getNamespaceResolver();
        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(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                prefix = schema.getNamespaceResolver().generatePrefix(Constants.SCHEMA_PREFIX);
            } else if (URI.equals(Constants.REF_URL)) {
                prefix = schema.getNamespaceResolver().generatePrefix(Constants.REF_PREFIX);

                if(!importExists(schema, URI, SWA_REF_IMPORT)){
                    Import schemaImport = new Import();
                    schemaImport.setSchemaLocation(SWA_REF_IMPORT);
                    schemaImport.setNamespace(URI);
                    schema.getImports().add(schemaImport);
                }
            } else {
                prefix = schema.getNamespaceResolver().generatePrefix();
            }
            schema.getNamespaceResolver().put(prefix, URI);
        }
        return prefix;
    }

    public void addGlobalElements(Map<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) {
                        continue;
                    }

                    if (targetSchema.getTopLevelElements().get(next.getLocalPart()) == null) {
                        Element element = new Element();
                        element.setName(next.getLocalPart());
                        element.setNillable(nextElement.isNillable());
                        JavaClass javaClass = nextElement.getJavaType();

                        //First check for built in type
                        QName schemaType = (QName) helper.getXMLToJavaTypeMap().get(javaClass.getRawName());
                        if (schemaType != null) {
                            element.setType(Constants.SCHEMA_PREFIX + COLON + 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 = Constants.BASE_64_BINARY_QNAME;
                            if(nextElement.getTypeMappingInfo() != null) {
                                if(nextElement.isXmlAttachmentRef()) {
                                    schemaType = Constants.SWA_REF_QNAME;
                                }
                                if (nextElement.getXmlMimeType() != null) {
                                    element.getAttributesMap().put(Constants.EXPECTED_CONTENT_TYPES_QNAME, nextElement.getXmlMimeType());
                                }
                            }
                            String prefix = getOrGeneratePrefixForNamespace(schemaType.getNamespaceURI(), targetSchema);
                            element.setType(prefix + COLON + schemaType.getLocalPart());
                        } else if (areEquals(javaClass, CoreClassConstants.XML_GREGORIAN_CALENDAR)) {
                            schemaType = Constants.ANY_SIMPLE_TYPE_QNAME;
                            element.setType(Constants.SCHEMA_PREFIX + COLON + schemaType.getLocalPart());
                        } else {

                            TypeInfo type = this.typeInfo.get(javaClass.getQualifiedName());
                            if (type != null) {
                                String typeName = null;
                                if (type.isComplexType()) {
                                    typeName = type.getComplexType().getName();
                                } else {
                                    typeName = type.getSimpleType().getName();
                                }
                                // may need an anonymous complex type
                                if (typeName == null) {
                                    Schema schema = getSchemaForNamespace(next.getNamespaceURI());
                                    ComplexType cType = createComplexTypeForClass(javaClass, type);
                                    TypeDefParticle particle = null;
                                    if(cType.getComplexContent() != null && cType.getComplexContent().getExtension() != null) {
                                        particle = cType.getComplexContent().getExtension().getTypeDefParticle();
                                    } else {
                                        particle = cType.getTypeDefParticle();
                                    }
                                    addToSchemaType(type, type.getPropertyList(), particle, cType, schema);
                                    targetSchema = schema;
                                    element.setComplexType(cType);
                                } else {
                                    //  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(EMPTY_STRING))) {
                                            element.setType(prefix + COLON + typeName);
                                        } else {
                                            element.setType(typeName);
                                        }
                                    } else {
                                        Schema complexTypeSchema = getSchemaForNamespace(type.getClassNamespace());
                                        String complexTypeSchemaNS = type.getClassNamespace();
                                        if (complexTypeSchemaNS == null) {
                                            complexTypeSchemaNS = EMPTY_STRING;
                                        }
                                        addImportIfRequired(targetSchema, complexTypeSchema, type.getClassNamespace());

                                        String prefix = targetSchema.getNamespaceResolver().resolveNamespaceURI(complexTypeSchemaNS);
                                        if (prefix != null) {
                                            element.setType(prefix + COLON + typeName);
                                        } else {
                                            element.setType(typeName);
                                        }
                                    }
                                }
                            }
                        }
                        if (nextElement.getSubstitutionHead() != null) {
                            String subLocal = nextElement.getSubstitutionHead().getLocalPart();
                            String subNamespace = nextElement.getSubstitutionHead().getNamespaceURI();
                            String prefix = getPrefixForNamespace(targetSchema, subNamespace);
                            if (prefix == null || prefix.equals(EMPTY_STRING)) {
                                element.setSubstitutionGroup(subLocal);
                            } else {
                                element.setSubstitutionGroup(prefix + COLON + 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);
                    }
                }
            }
        }
    }

    /**
     * Return the Map of SchemaTypeInfo instances.  The Map is keyed on JavaClass
     * qualified name.
     *
     * @return
     */
    public Map<String, SchemaTypeInfo> getSchemaTypeInfo() {
        return this.schemaTypeInfo;
    }

    private boolean importExists(Schema schema, String importNsURI, String schemaLocation) {
        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(schemaLocation)) {
                if ("".equals(importNsURI)) {
                    if (nextImport.getNamespace() == null) {
                        return true;
                    }
                } else {
                    if (importNsURI.equals(nextImport.getNamespace())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean addImportIfRequired(Schema sourceSchema, Schema importSchema, String importNamespace) {
        if (importSchema != sourceSchema && !(importNamespace != null && importNamespace.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI))) {
            String schemaName = null;
            if(javax.xml.XMLConstants.XML_NS_URI.equals(importNamespace)){
                schemaName = Constants.XML_NAMESPACE_SCHEMA_LOCATION;
            }else{
                if (importSchema != null) {
                    schemaName = importSchema.getName();
                } else if (importNamespace != null) {
                    NamespaceInfo nInfo = getNamespaceInfoForNamespace(importNamespace);
                    schemaName = nInfo.getLocation();
                }
            }
            // may need to relativize the schema name
            if (schemaName != null && importSchema != null) {
                URI relativizedURI = null;
                try {
                    // need to strip off the last slash and the file name that follows
                    String schemaPath = sourceSchema.getName();
                    int idx;
                    if ((idx = schemaPath.lastIndexOf(SLASH)) != -1) {
                        schemaPath = schemaPath.substring(0, idx);
                    }
                    URI baseURI = new URI(schemaPath);
                    URI importURI = new URI(schemaName);
                    relativizedURI = baseURI.relativize(importURI);
                } catch (Exception e) {
                    // at this point we will leave schemaName as is
                    relativizedURI = null;
                }
                if (relativizedURI != null) {
                    schemaName = relativizedURI.getPath();
                }
            }

            if (schemaName != null && !importExists(sourceSchema, importNamespace, schemaName)) {
                Import schemaImport = new Import();
                schemaImport.setSchemaLocation(schemaName);
                if (importNamespace != null && !importNamespace.equals(EMPTY_STRING)) {
                    schemaImport.setNamespace(importNamespace);
                }
                sourceSchema.getImports().add(schemaImport);
                if (schemaImport.getNamespace() != null) {
                    if(schemaImport.getNamespace().equals(javax.xml.XMLConstants.XML_NS_URI)) {
                        //make sure xml namespace is in the resolver so it gets declared
                        sourceSchema.getNamespaceResolver().put(javax.xml.XMLConstants.XML_NS_PREFIX, javax.xml.XMLConstants.XML_NS_URI);
                    }
                    String prefix = sourceSchema.getNamespaceResolver().resolveNamespaceURI(importNamespace);
                    //Don't need to generate prefix for default namespace
                    if (prefix == null && !importNamespace.equals(EMPTY_STRING)) {
                        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 tgtCanonicalName
     * @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());
    }

    /**
     * Return the type name for a given Property.
     *
     * @param next
     * @param javaType
     * @param theSchema
     * @return
     */
    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();
            }
            return getOrGeneratePrefixForNamespace(schemaType.getNamespaceURI(), theSchema) + COLON + schemaType.getLocalPart();
        }
        return Constants.SCHEMA_PREFIX + Constants.COLON + Constants.ANY_SIMPLE_TYPE;
    }

    /**
     * Return the qualified (if necessary) type name for a given Property.
     *
     * @param prop
     * @param schema
     * @return
     */
    private String getQualifiedTypeName(Property prop, Schema schema) {
        JavaClass javaType = prop.getActualType();
        String typeName = getTypeName(prop, javaType, schema);
        // may need to qualify the type
        if (typeName != null && !typeName.contains(COLON)) {
            String prefix = getPrefixForNamespace(schema, schema.getTargetNamespace());
            if (prefix != null) {
                typeName = prefix + COLON + typeName;
            }
        }
        return typeName;
    }

    /**
     * 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 isChoice
     * @param next
     * @return
     */
    protected AddToSchemaResult buildSchemaComponentsForXPath(XPathFragment frag, AddToSchemaResult xpr, boolean isChoice, Property next) {
        boolean isAny = next.isAny() || next.isAnyAttribute();
        TypeDefParticle currentParticle = xpr.particle;
        Schema workingSchema = xpr.schema;

        // each nested choice on a collection will be unbounded
        boolean isUnbounded = false;
        if(currentParticle != null) {
            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());
        //boolean isNextAttribute = (frag.getNextFragment() != null) && (frag.getNextFragment().isAttribute());
        // 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 {
                XPathFragment nextFragment = frag.getNextFragment();
                if (frag.containsIndex() || frag.getPredicate() != null || (!next.isXmlList() && null != nextFragment && nextFragment.isAttribute() && helper.isCollectionType(next.getType()))) {
                    currentElement.setMaxOccurs(Occurs.UNBOUNDED);
                }
                particle = new Sequence();
            }
            cType.setTypeDefParticle(particle);
            currentElement.setComplexType(cType);
        } else {
            //if the current element already exists, we may need to change it's type
            XPathFragment nextFrag = frag.getNextFragment();
            if(nextFrag != null && nextFrag.isAttribute()) {
                if(currentElement.getType() != null && currentElement.getComplexType() == null) {
                    //there's already a text mapping to this element, so
                    //attributes can be added by making it complex with
                    //simple content.
                    SimpleType type = currentElement.getSimpleType();
                    if(type != null) {
                        ComplexType cType = new ComplexType();
                        cType.setSimpleContent(new SimpleContent());
                        Extension extension = new Extension();
                        extension.setBaseType(type.getRestriction().getBaseType());
                        cType.getSimpleContent().setExtension(extension);
                        currentElement.setSimpleType(null);
                        currentElement.setComplexType(cType);
                    } else {
                        String eType = currentElement.getType();
                        ComplexType cType = new ComplexType();
                        SimpleContent sContent = new SimpleContent();
                        Extension extension = new Extension();
                        extension.setBaseType(eType);
                        sContent.setExtension(extension);
                        cType.setSimpleContent(sContent);
                        currentElement.setType(null);
                        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 == null || (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
                    // if fragSchema is null, just generate the ref
                    if(fragSchema != null) {
                        Attribute globalAttribute = null;
                        globalAttribute = (Attribute) fragSchema.getTopLevelAttributes().get(frag.getLocalName());
                        if (globalAttribute == null) {
                            globalAttribute = createGlobalAttribute(frag, workingSchema, fragSchema, next);
                        }
                    } else {
                        //may need to add an import
                        addImportIfRequired(workingSchema, null, fragUri);
                    }
                    // add the attribute ref to the current element
                    String attributeRefName;
                    if (fragUri.equals(targetNS)) {
                        String prefix = fragSchema.getNamespaceResolver().resolveNamespaceURI(fragUri);
                        attributeRefName = prefix + COLON + frag.getLocalName();
                    } else {
                        attributeRefName = frag.getShortName();
                    }
                    TypeDefParticleOwner type;
                    if(currentParticle != null) {
                        type = currentParticle.getOwner();
                    } else {
                        type = xpr.simpleContentType;
                    }
                    if (type instanceof ComplexType) {
                        createRefAttribute(attributeRefName, (ComplexType)type);
                    }
                    // 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, next, (lastFrag && !isAny));
                }
                // if the current element doesn't exist set a ref and add it to the sequence
                if (!currentElementExists) {
                    // use prefix from the working schema's resolver - add prefix/uri pair if necessary
                    String fragPrefix = workingSchema.getNamespaceResolver().resolveNamespaceURI(fragUri);
                    if (fragPrefix == null) {
                        fragPrefix = workingSchema.getNamespaceResolver().generatePrefix(frag.getPrefix());
                        workingSchema.getNamespaceResolver().put(fragPrefix, fragUri);
                    }
                    currentElement = createRefElement(fragPrefix + COLON + frag.getLocalName(), currentParticle);
                    if (frag.containsIndex() || frag.getPredicate() != null || helper.isCollectionType(next.getType())) {
                        currentElement.setMaxOccurs(Occurs.UNBOUNDED);
                    }
                    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());
                Integer minOccurs = next.getMinOccurs();
                if (minOccurs != null) currentElement.setMinOccurs(String.valueOf(minOccurs));
                else                   currentElement.setMinOccurs(Occurs.ZERO);
                currentParticle.addElement(currentElement);
            }
            // set the correct particle to use/return
            if(currentElement.getComplexType() != null) {
                if(currentElement.getComplexType().getTypeDefParticle() == null) {
                    //complexType with simple-content
                    xpr.simpleContentType = currentElement.getComplexType();
                    xpr.particle = null;
                } else {
                    xpr.particle = currentElement.getComplexType().getTypeDefParticle();
                }
            } else {
                //If there's no complex type, we're building the path through an element with
                //a simple type. In order to build the path through this
                //element, switch to a complex type with simple content.
                SimpleType type = currentElement.getSimpleType();
                if(type != null) {
                    ComplexType cType = new ComplexType();
                    cType.setSimpleContent(new SimpleContent());
                    Extension extension = new Extension();
                    extension.setBaseType(type.getRestriction().getBaseType());
                    cType.getSimpleContent().setExtension(extension);
                    currentElement.setSimpleType(null);
                    currentElement.setComplexType(cType);
                    xpr.particle = null;
                    xpr.simpleContentType = cType;
                } else {
                    String eType = currentElement.getType();
                    ComplexType cType = new ComplexType();
                    SimpleContent sContent = new SimpleContent();
                    Extension extension = new Extension();
                    extension.setBaseType(eType);
                    sContent.setExtension(extension);
                    cType.setSimpleContent(sContent);
                    currentElement.setType(null);
                    currentElement.setComplexType(cType);
                    xpr.particle = null;
                    xpr.simpleContentType = cType;
                }
            }
        }
        // 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, 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.
     *
     * Note that ref takes precidence, so if either has a ref set name equality
     * will not be performed.
     *
     * @param elementName the non-null element name to look for
     * @param refString if the element is a ref, this will be the prefix qualified element name
     * @param particle the sequence/choice/all to search for an existing element
     * @return
     */
    protected Element elementExistsInParticle(String elementName, String refString, TypeDefParticle particle) {
        if (particle == null || 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;
                }
                // case #1 - refString is null
                if (refString == null) {
                    // if element has a null ref value and the element names are equal, or element has a non-null
                    // ref that is not equal to its name, and the element names are equal, we found a match
                    if ((element.getRef() == null || (element.getRef() != null && element.getRef().equals(element.getName()))) && elementName.equals(element.getName())) {
                        return element;
                    }
                }
                // case #2 - refString equals elementName
                else if (refString.equals(elementName)) {
                    // if element has a ref equal to refString, or no ref but element names are equal, we found a match
                    if ((element.getRef() != null && element.getRef().equals(refString)) || (element.getRef() == null && elementName.equals(element.getName()))) {
                        return element;
                    }
                }
                // case #3 - refString is different than elementName
                else if (element.getRef() != null && element.getRef().equals(refString)) {
                    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 prop
     * @return
     */
    public Attribute createGlobalAttribute(XPathFragment frag, Schema workingSchema, Schema fragSchema, Property prop) {
        Attribute gAttribute = new Attribute();
        gAttribute.setName(frag.getLocalName());
        gAttribute.setType(getQualifiedTypeName(prop, fragSchema));
        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 XPathFragment which wil lbe used to create the global element
     * @param workingSchema current schema
     * @param fragSchema frag's schema
     * @param isChoice indicates if we need to construct a choice
     * @param isUnbounded maxOccurs setting for choice
     * @param prop property which owns the xml-path
     * @param shouldSetType if this is the last fragment in the xml-path and not an 'any', we should set the type
     * @return
     */
    public Element createGlobalElement(XPathFragment frag, Schema workingSchema, Schema fragSchema, boolean isChoice, boolean isUnbounded, Property prop, boolean shouldSetType) {
        Element gElement = new Element();
        gElement.setName(frag.getLocalName());
        if (shouldSetType) {
            gElement.setType(getQualifiedTypeName(prop, fragSchema));
        } else {
            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 building schema components.  It will hold the
     * TypeDefParticle (all, sequence, choice), ComplexType, and/or Schema that are to be
     * used by the method that is processing the given property.
     *
     */
    private static final class AddToSchemaResult {
        ComplexType type;
        TypeDefParticle particle;
        Schema schema;
        ComplexType simpleContentType;

        AddToSchemaResult(TypeDefParticle particle, Schema schema) {
            this.particle = particle;
            this.schema = schema;
        }

        AddToSchemaResult(TypeDefParticle particle, ComplexType type) {
            this.particle = particle;
            this.type = type;
        }
    }

    /**
     * Convenience method for processing XmlWriteTransformer(s) for a given property.
     * Required schema components will be generated and set accordingly.
     *
     * @param property the property containing one or more XmlWriteTransformers.
     * @param typeInfo the TypeInfo that owns the property
     * @param compositor sequence/choice/all to modify
     * @param type ComplexType which compositor(s) should be added to
     * @param schema current schema which ComplextType will be added to
     */
    private void addTransformerToSchema(Property property, TypeInfo typeInfo, TypeDefParticle compositor, ComplexType type, Schema schema) {
        java.util.List<Property> props = getTransformerPropertyBuilder(property, typeInfo).buildProperties();
        addToSchemaType(typeInfo, props, compositor, type, schema);
    }

    /**
     * Returns TransformerPropertyBuilder which builds properties from xml transformers.
     *
     * @param property property holding xml transformers
     * @param typeInfo typeInfo with transformer class
     * @return  transformer property builder
     */
    protected TransformerPropertyBuilder getTransformerPropertyBuilder(Property property, TypeInfo typeInfo) {
        return new TransformerPropertyBuilder(property, typeInfo, helper, ATT);
    }

    /**
     * Process a given XmlPath, and create the required schema components.  The last
     * fragment may not be processed; in this case the returned XmlPathResult is
     * used to set the current schema and parent complex type, then the owning
     * property is processed as per usual. Note the last fragment may be processed
     * if it has a namespace or is an 'any'.
     *
     * @param property the property containing the XmlPath for which schema components are to be built
     * @param compositor the sequence/choice/all to modify
     * @param schema the schema being built when this method is called - this could
     *        if the XmlPath contains an entry that references a different namespace
     * @param isChoice indicates if the given property is a choice property
     * @param type the ComplexType which compositor(s) should be added to
     * @return AddToSchemaResult containing current schema and sequence/all/choice build
     *         based on the given XmlPath
     */
    private AddToSchemaResult addXPathToSchema(Property property, TypeDefParticle compositor, Schema schema, boolean isChoice, ComplexType type) {
        // '.' xml-path requires special handling
        if (property.getXmlPath().equals(DOT)) {
            TypeInfo info = typeInfo.get(property.getActualType().getQualifiedName());
            JavaClass infoClass = property.getActualType();
            addSelfProperties(infoClass, info, compositor, type);
            return null;
        }
        // create the XPathFragment(s) for the path
        Field xfld = new XMLField(property.getXmlPath());
        xfld.setNamespaceResolver(schema.getNamespaceResolver());
        xfld.initialize();
        // build the schema components for the xml-path
        return buildSchemaComponentsForXPath(xfld.getXPathFragment(), new AddToSchemaResult(compositor, schema), isChoice, property);
    }

    private void addSelfProperties(JavaClass aJavaClass, TypeInfo info, TypeDefParticle compositor, ComplexType type) {
        // Recursively add all properties from aJavaClass and its superclasses, adding superclass properties first.
        if (aJavaClass.getSuperclass() != null) {
            TypeInfo superInfo = typeInfo.get(aJavaClass.getSuperclass().getQualifiedName());
            if (superInfo != null) {
                addSelfProperties(aJavaClass.getSuperclass(), superInfo, compositor, type);
            }
        }
        addToSchemaType(info, info.getPropertyList(), compositor, type, info.getSchema());
    }

    /**
     * Convenience method for processing an XmlElementWrapper for a given property. Required schema
     * components will be generated and set accordingly.
     *
     * @param property the property containing an XmlElementWrapper
     * @param schema the schema currently being generated
     * @param compositor sequence/choice/all that the generated wrapper Element will be added to
     * @return AddToSchemaResult containing current ComplexType and TypeDefParticle
     */
    private AddToSchemaResult addXmlElementWrapperToSchema(Property property, Schema schema, TypeDefParticle compositor) {
        XmlElementWrapper wrapper = property.getXmlElementWrapper();
        Element wrapperElement = new Element();
        String name = wrapper.getName();
        // 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(XMLProcessor.DEFAULT) && !wrapperNS.equals(schema.getTargetNamespace())) {
            wrapperElement.setMinOccurs(Occurs.ONE);
            wrapperElement.setMaxOccurs(Occurs.ONE);

            String prefix = getOrGeneratePrefixForNamespace(wrapperNS, schema);
            wrapperElement.setRef(prefix + COLON + name);
            compositor.addElement(wrapperElement);
            // assume that the element exists and does not need to be created
            return null;
        }
        wrapperElement.setName(name);
        if (wrapper.isRequired()) {
            wrapperElement.setMinOccurs(Occurs.ONE);
        } else {
            wrapperElement.setMinOccurs(Occurs.ZERO);
        }
        if (!wrapperNS.equals(XMLProcessor.DEFAULT)) {
            String lookupNamespace = schema.getTargetNamespace();
            if (lookupNamespace == null) {
                lookupNamespace = EMPTY_STRING;
            }
            NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace);
            boolean isElementFormQualified = false;
            if (namespaceInfo != null) {
                isElementFormQualified = namespaceInfo.isElementFormQualified();
            }
            shouldAddRefAndSetForm(wrapperElement, wrapperNS, lookupNamespace,
                    isElementFormQualified, true);
        }
        compositor.addElement(wrapperElement);
        ComplexType wrapperType = new ComplexType();
        Sequence wrapperSequence = new Sequence();
        wrapperType.setSequence(wrapperSequence);
        wrapperElement.setComplexType(wrapperType);
        return new AddToSchemaResult(wrapperSequence, wrapperType);
    }

    /**
     * Build an Attribute with name and type set.  This method will typically be
     * called when processing an XPath that has no associated Property that can
     * be used to build an Attribute, such as in the case of XmlJoinNodes.
     *
     * @param attributeName name of the Attribute
     * @param typeName type of the Attribute
     * @return
     */
    private Attribute buildAttribute(QName attributeName, String typeName) {
        Attribute attribute = new Attribute();
        attribute.setName(attributeName.getLocalPart());
        attribute.setType(typeName);
        return attribute;
    }

    /**
     * Build an Attribute based on a given Property.
     *
     * @param property the Property used to build the Attribute
     * @param schema the schema currently being generated
     * @return
     */
    private Attribute buildAttribute(Property property, Schema schema) {
        Attribute attribute = new Attribute();

        QName attributeName = property.getSchemaName();
        attribute.setName(attributeName.getLocalPart());
        if (property.isRequired()) {
            attribute.setUse(Attribute.REQUIRED);
        }
        String fixedValue = property.getFixedValue();
        if (fixedValue != null){
            attribute.setFixed(fixedValue);
        }
        // Check to see if it's a collection
        TypeInfo info = typeInfo.get(property.getActualType().getQualifiedName());
        String typeName = getTypeNameForComponent(property, schema, property.getActualType(), attribute, false);
        if (isCollectionType(property)) {
            if(!property.isXmlList() && null != property.getXmlPath() && property.getXmlPath().contains("/")) {
                attribute.setType(typeName);
            } else {
                // 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(COLON)) {
                if (info.getSchema() == schema) {
                    String uri = schema.getTargetNamespace();
                    if(uri == null) {
                        uri = EMPTY_STRING;
                    }
                    String prefix = getPrefixForNamespace(schema, uri);
                    if (prefix != null) {
                        typeName = prefix + COLON + typeName;
                    }
                }
            }
            attribute.setType(typeName);
        }
        return attribute;
    }

    /**
     * Convenience method for processing an attribute property. Required schema
     * components will be generated and set accordingly.
     *
     * @param attribute the attribute property to be processed
     * @param schema the schema currently being generated
     * @param type the ComplexType which compositor(s) should be added to
     */
    private void addAttributeToSchema(Attribute attribute, QName attributeName, Schema schema, ComplexType type) {
        String lookupNamespace = schema.getTargetNamespace();
        if (lookupNamespace == null) {
            lookupNamespace = EMPTY_STRING;
        }
        NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace);
        boolean isAttributeFormQualified = false;
        if (namespaceInfo != null) {
            isAttributeFormQualified = namespaceInfo.isAttributeFormQualified();
        }

        boolean addRef = shouldAddRefAndSetForm(attribute, attributeName.getNamespaceURI(), lookupNamespace, isAttributeFormQualified, false);
        if(addRef){
            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);
            }
            Attribute reference = new Attribute();
            String prefix = getPrefixForNamespace(schema, attributeName.getNamespaceURI());
            if (prefix == null) {
                reference.setRef(attribute.getName());
            } else {
                reference.setRef(prefix + COLON + attribute.getName());
            }
            if (type.getSimpleContent() != null) {
                type.getSimpleContent().getExtension().getOrderedAttributes().add(reference);
            } else {
                type.getOrderedAttributes().add(reference);
            }
        } else {
            if (type.getSimpleContent() != null) {
                type.getSimpleContent().getExtension().getOrderedAttributes().add(attribute);
            } else if (type.getComplexContent() != null) {
                type.getComplexContent().getExtension().getOrderedAttributes().add(attribute);
            } else {
                type.getOrderedAttributes().add(attribute);
            }
        }
    }

    private boolean shouldAddRefAndSetForm(SimpleComponent sc, String simpleComponentNamespace, String lookupNamespace, boolean formQualified, boolean isElement){
        if(sc.getRef() != null){
            return true;
        }
        boolean addRef = false;
        boolean sameNamespace = simpleComponentNamespace.equals(lookupNamespace);

        if (formQualified && !sameNamespace){
            if(simpleComponentNamespace.equals(EMPTY_STRING)){
                sc.setForm(Constants.UNQUALIFIED);
            }else{
                addRef = true;
            }
        } else if(!formQualified  && !simpleComponentNamespace.equals(EMPTY_STRING)){
            if(sameNamespace && isElement){
                sc.setForm(Constants.QUALIFIED);
            }else{
                addRef = true;
            }
        }
        return addRef;
    }

    /**
     * Convenience method for processing an any attribute property. Required
     * schema components will be generated and set accordingly.
     *
     * @param type the ComplexType which compositor(s) should be added to
     */
    private void addAnyAttributeToSchema(ComplexType type) {
        AnyAttribute anyAttribute = new AnyAttribute();
        anyAttribute.setProcessContents(SKIP);
        anyAttribute.setNamespace(Constants.ANY_NAMESPACE_OTHER);
        if (type.getSimpleContent() != null) {
            SimpleContent content = type.getSimpleContent();
            if(content.getExtension() != null){
                content.getExtension().setAnyAttribute(anyAttribute);
            }else if(content.getRestriction() != null){
                content.getRestriction().setAnyAttribute(anyAttribute);
            }
        } else {
            type.setAnyAttribute(anyAttribute);
        }
    }

    /**
     * Convenience method for processing an any property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the choice property to be processed
     * @param compositor the sequence/choice/all to modify
     */
    private void addAnyToSchema(Property property, TypeDefParticle compositor) {
        addAnyToSchema(property, compositor, isCollectionType(property)|| property.getType().isArray(), Constants.ANY_NAMESPACE_OTHER);
    }

    /**
     * Convenience method for processing an any property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the choice property to be processed
     * @param compositor the sequence/choice/all to modify
     * @param isCollection if true will be unbounded
     */
    private void addAnyToSchema(Property property, TypeDefParticle compositor, boolean isCollection) {
        addAnyToSchema(property, compositor, isCollection, Constants.ANY_NAMESPACE_OTHER);
    }

    /**
     * Convenience method for processing an any property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the choice property to be processed
     * @param compositor the sequence/choice/all to modify
     * @param isCollection if true will be unbounded
     * @param anyNamespace value for the Any's namespace attribute
     */
    private void addAnyToSchema(Property property, TypeDefParticle compositor, boolean isCollection, String anyNamespace) {
        Any any = new Any();
        any.setNamespace(anyNamespace);
        if (property.isLax()) {
            any.setProcessContents(Any.LAX);
        } else {
            any.setProcessContents(SKIP);
        }
        if (isCollection) {
            any.setMinOccurs(Occurs.ZERO);
            any.setMaxOccurs(Occurs.UNBOUNDED);
        }
        if (compositor instanceof Sequence) {
            ((Sequence) compositor).addAny(any);
        } else if (compositor instanceof Choice) {
            ((Choice) compositor).addAny(any);
        }
    }

    /**
     * Convenience method for processing a choice property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the choice property to be processed
     * @param typeInfo the TypeInfo that the given property belongs to
     * @param type the ComplexType which compositor(s) should be added to
     * @param compositor the sequence/choice/all to modify
     * @param schema the schema being built
     */
    private void addChoiceToSchema(Property property, TypeInfo typeInfo, ComplexType type, TypeDefParticle compositor, Schema schema) {
        Choice choice = new Choice();
        if (property.getGenericType() != null) {
            choice.setMaxOccurs(Occurs.UNBOUNDED);
        }
        ArrayList<Property> choiceProperties = (ArrayList<Property>) property.getChoiceProperties();
        addToSchemaType(typeInfo, choiceProperties, choice, type, schema);
        if (compositor instanceof Sequence) {
            ((Sequence) compositor).addChoice(choice);
        } else if (compositor instanceof Choice) {
            ((Choice) compositor).addChoice(choice);
        }
    }

    /**
     * Convenience method for processing a reference property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the choice property to be processed
     * @param compositor the sequence/choice/all to modify
     * @param schema the schema being built
     */
    private void addReferenceToSchema(Property property, Schema schema, TypeDefParticle compositor) {
        java.util.List<ElementDeclaration> referencedElements = property.getReferencedElements();
        if (referencedElements.size() == 1 && !property.isAny()) {
            // if only a single reference, just add the element.
            Element element = new Element();
            ElementDeclaration decl = referencedElements.get(0);
            String localName = decl.getElementName().getLocalPart();

            String prefix = getPrefixForNamespace(schema, decl.getElementName().getNamespaceURI());
            if (decl.getScopeClass() == GLOBAL.class){
                if (prefix == null || prefix.equals(EMPTY_STRING)) {
                    element.setRef(localName);
                } else {
                    element.setRef(prefix + COLON + localName);
                }
            } else {
                element.setType(getTypeName(property, decl.getJavaType(), schema));
                element.setName(localName);
            }
            if (property.getGenericType() != null) {
                element.setMinOccurs(Occurs.ZERO);
                element.setMaxOccurs(Occurs.UNBOUNDED);
            }else if(!property.isRequired()){
                element.setMinOccurs(Occurs.ZERO);
            }
            compositor.addElement(element);
        } else {
            // otherwise, add a choice of referenced elements.
            Choice choice = new Choice();
            if (property.getGenericType() != null) {
                choice.setMaxOccurs(Occurs.UNBOUNDED);
            }
            if (!property.isRequired()){
                choice.setMinOccurs(Occurs.ZERO);
            }
            for (ElementDeclaration elementDecl : referencedElements) {
                Element element = new Element();
                String localName = elementDecl.getElementName().getLocalPart();

                String prefix = getPrefixForNamespace(schema, elementDecl.getElementName().getNamespaceURI());
                if (elementDecl.getScopeClass() == GLOBAL.class){
                    if (prefix == null || prefix.equals(EMPTY_STRING)) {
                        element.setRef(localName);
                    } else {
                        element.setRef(prefix + COLON + localName);
                    }
                } else {
                    Schema referencedSchema = getSchemaForNamespace(elementDecl.getElementName().getNamespaceURI());
                    element.setType(getTypeName(property, elementDecl.getJavaType(), referencedSchema));
                    element.setName(localName);
                }
                choice.addElement(element);
            }
            // handle XmlAnyElement case
            if (property.isAny()) {
                addAnyToSchema(property, choice, false);
            }
            if (compositor instanceof Sequence) {
                ((Sequence) compositor).addChoice(choice);
            } else if (compositor instanceof Choice) {
                ((Choice) compositor).addChoice(choice);
            }
        }
    }

    /**
     * Convenience method for processing a reference property. Required
     * schema components will be generated and set accordingly.
     *
     * @param property the map property to be processed
     * @param element schema Element a new complex type will be added to
     * @param schema the schema being built
     * @param typeInfo the TypeInfo that the given property belongs to
     */
    private void addMapToSchema(Property property, Element element, Schema schema, TypeInfo typeInfo) {
        ComplexType entryComplexType = new ComplexType();
        Sequence entrySequence = new Sequence();

        Element keyElement = new Element();
        keyElement.setName(Property.DEFAULT_KEY_NAME);
        keyElement.setMinOccurs(Occurs.ZERO);

        JavaClass keyType = property.getKeyType();
        JavaClass valueType = property.getActualValueType();

        if (keyType == null) {
            keyType = helper.getJavaClass(Object.class);
        }

        if (valueType == null) {
            valueType = helper.getJavaClass(Object.class);
        }

        String typeName;
        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(schema, keyElementSchema, keySchemaType.getNamespaceURI());
            }
            String prefix;
            if (keySchemaType.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                prefix = Constants.SCHEMA_PREFIX;
            } else {
                prefix = getPrefixForNamespace(schema, keySchemaType.getNamespaceURI());
            }
            if (prefix != null && !prefix.equals(EMPTY_STRING)) {
                typeName = prefix + COLON + 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(schema, valueElementSchema, valueSchemaType.getNamespaceURI());
            }
            String prefix;
            if (valueSchemaType.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                prefix = Constants.SCHEMA_PREFIX;
            } else {
                prefix = getPrefixForNamespace(schema, valueSchemaType.getNamespaceURI());
            }
            if (prefix != null && !prefix.equals(EMPTY_STRING)) {
                typeName = prefix + COLON + valueSchemaType.getLocalPart();
            } else {
                typeName = valueSchemaType.getLocalPart();
            }
            if (property.getValueGenericType() != null) {
                valueElement.setMaxOccurs(Occurs.UNBOUNDED);
            }
            valueElement.setType(typeName);
        }

        entrySequence.addElement(valueElement);
        entryComplexType.setSequence(entrySequence);

        JavaClass descriptorClass = null;
        if (null != typeInfo.getDescriptor()) {
            descriptorClass = helper.getJavaClass(typeInfo.getDescriptor().getJavaClassName());
        }

        JavaClass mapValueClass = helper.getJavaClass(MapValue.class);

        if (null != descriptorClass && 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);
        }
    }

    /**
     * Convenience method that adds an element ref to a given schema.
     *
     * @param schema the schema being built
     * @param compositor the sequence/choice/all the new reference will be added to
     * @param referencedElement the element being referenced
     * @param referencedElementURI the URI of the element being referenced
     */
    private void addElementRefToSchema(Schema schema, TypeDefParticle compositor, Element referencedElement, String referencedElementURI) {
        Element reference = new Element();
        reference.setMinOccurs(referencedElement.getMinOccurs());
        reference.setMaxOccurs(referencedElement.getMaxOccurs());
        Schema attributeSchema = this.getSchemaForNamespace(referencedElementURI);
        if (attributeSchema != null && attributeSchema.getTopLevelElements().get(referencedElement.getName()) == null) {
            // reset min/max occurs as they aren't applicable for global elements
            referencedElement.setMinOccurs(null);
            referencedElement.setMaxOccurs(null);
            // don't overwrite global elements; may have been defined by a type
            attributeSchema.getTopLevelElements().put(referencedElement.getName(), referencedElement);
        }
        String prefix = getPrefixForNamespace(schema, referencedElementURI);
        if (prefix == null) {
            reference.setRef(referencedElement.getName());
        } else {
            reference.setRef(prefix + COLON + referencedElement.getName());
        }
        // make sure a ref doesn't already exist before adding this one
        if (elementExistsInParticle(reference.getName(), reference.getRef(), compositor) == null) {
            compositor.addElement(reference);
        }
    }

    /**
     * Build an Element with name, type and possibly minOccurs set.  This method will
     * typically be called when processing an XPath that has no associated Property
     * that can be used to build an Element, such as in the case of XmlJoinNodes.
     *
     * @param elementName name of the Element
     * @param elementType type of the Element
     * @param isAll indicates if the Element will be added to an All structure
     * @return
     */
    private Element buildElement(String elementName, String elementType, boolean isAll) {
        Element element = new Element();
        // Set minOccurs based on the 'required' flag
        if (!(isAll)) {
            element.setMinOccurs(Occurs.ZERO);
        }
        element.setName(elementName);
        element.setType(elementType);
        return element;
    }

    /**
     * Build an Element based on a given Property.
     *
     * @param property the Property used to build the Element
     * @param isAll true if the Element will be added to an All structure
     * @param schema the schema currently being built
     * @param typeInfo the TypeInfo that owns the given Property
     * @return
     */
    private Element buildElement(Property property, boolean isAll, Schema schema, TypeInfo typeInfo) {
        Element element = new Element();

        // handle nillable
        if (property.shouldSetNillable()) {
            if (property.isNotNullAnnotated()) throw BeanValidationException.notNullAndNillable(property.getPropertyName());
            element.setNillable(true);
        }
        // handle defaultValue
        if (property.isSetDefaultValue()) {
            element.setDefaultValue(property.getDefaultValue());
        }
        // handle mime-type
        if (property.getMimeType() != null) {
            element.getAttributesMap().put(Constants.EXPECTED_CONTENT_TYPES_QNAME, property.getMimeType());
        }

        QName elementName = property.getSchemaName();
        String elementNamespace = elementName.getNamespaceURI();
        String lookupNamespace = schema.getTargetNamespace();
        if (lookupNamespace == null) {
            lookupNamespace = EMPTY_STRING;
        }
        NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace, getPackageName(typeInfo));
        boolean isElementFormQualified = false;
        if (namespaceInfo != null) {
            isElementFormQualified = namespaceInfo.isElementFormQualified();
        }
        // handle element reference

        boolean addRef = shouldAddRefAndSetForm(element, elementNamespace, lookupNamespace, isElementFormQualified, true);

        if(addRef){
            schema = this.getSchemaForNamespace(elementNamespace);
        }

        JavaClass javaType = property.getActualType();
        element.setName(elementName.getLocalPart());
        String typeName = getTypeNameForComponent(property, schema, javaType, element, true);

        if (property.getGenericType() != null) {
            if (property.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);
            }
            // handle map property
        } else if (property.isMap()) {
            addMapToSchema(property, element, schema, typeInfo);
        } else {
            element.setType(typeName);
        }

        // Set minOccurs based on the 'required' flag
        Integer minOccurs = property.getMinOccurs();
        if (minOccurs != null) {
            element.setMinOccurs(String.valueOf(minOccurs));
        } else {
            element.setMinOccurs(property.isRequired() ? Occurs.ONE : Occurs.ZERO);
        }
        // Overwrite maxOccurs if it has been explicitly set on property.
        Integer maxOccurs = property.getMaxOccurs();
        if (maxOccurs != null) element.setMaxOccurs(String.valueOf(maxOccurs));

        if (facets) {
            for (Facet facet : property.getFacets()) {
                processFacet(element, facet);
            }
        }
        return element;
    }

    private String getPackageName(TypeInfo typeInfo) {
        if (null != typeInfo && null != typeInfo.getDescriptor() && null != typeInfo.getDescriptor().getJavaClass() && null != typeInfo.getDescriptor().getJavaClass().getPackage()) {
            return typeInfo.getDescriptor().getJavaClass().getPackage().getName();
        }
        return null;
    }

    private void processFacet(Element element, Facet facet) {
        if (element.getSimpleType() == null) element.setSimpleType(new SimpleType());
        Restriction restriction = element.getSimpleType().getRestriction();
        if (restriction == null) {
            restriction = new Restriction(element.getType());
            element.getSimpleType().setRestriction(restriction);
        }
        element.setType(null); // Prevent error: "Cannot have both a 'type' attribute and an 'anonymous type' child".
        facet.accept(FacetVisitorHolder.VISITOR, restriction);
    }


    /**
     * @since 2.6
     * @author Marcel Valovy
     */
    private static final class FacetVisitorHolder {
        private static final FacetVisitor<Void, Restriction> VISITOR = new FacetVisitor<Void, Restriction>() {
            @Override
            public Void visit(DecimalMinFacet t, Restriction restriction) {
                if (t.isInclusive())    restriction.setMinInclusive(t.getValue());
                else                    restriction.setMinExclusive(t.getValue());
                return null;
            }

            @Override
            public Void visit(DecimalMaxFacet t, Restriction restriction) {
                if (t.isInclusive())    restriction.setMaxInclusive(t.getValue());
                else                    restriction.setMaxExclusive(t.getValue());
                return null;
            }

            @Override
            public Void visit(DigitsFacet t, Restriction restriction) {
                int fraction = t.getFraction();
                if (fraction > 0) {
                    restriction.setFractionDigits(fraction);
                    restriction.setTotalDigits(fraction + t.getInteger());
                } else if (fraction == 0) {
                    restriction.setTotalDigits(t.getInteger());
                }
                return null;
            }

            @Override
            public Void visit(MaxFacet t, Restriction restriction) {
                restriction.setMaxInclusive(String.valueOf(t.getValue()));
                return null;
            }

            @Override
            public Void visit(MinFacet t, Restriction restriction) {
                restriction.setMinInclusive(String.valueOf(t.getValue()));
                return null;
            }

            @Override
            public Void visit(PatternFacet t, Restriction restriction) {
                String regex = t.getRegexp();
                regex = introduceShorthands(regex);
                restriction.addPattern(regex);
                return null;
            }

            @Override
            public Void visit(PatternListFacet t, Restriction restriction) {
                for (PatternFacet pf : t.getPatterns()) {
                    String regex = pf.getRegexp();
                    regex = introduceShorthands(regex);
                    restriction.addPattern(regex);
                }
                return null;
            }

            @Override
            public Void visit(SizeFacet t, Restriction restriction) {
                int minLength = t.getMin();
                int maxLength = t.getMax();
                if (minLength == maxLength) {
                    restriction.setLength(minLength);
                } else {
                    // 0 is the default minBoundary.
                    if (minLength > 0) restriction.setMinLength(minLength);
                    // 2^31 is the default maxBoundary.
                    if (maxLength < Integer.MAX_VALUE) restriction.setMaxLength(maxLength);
                }
                return null;
            }
        };
    }

    /**
     * Convenience method that adds an element to a given schema.
     *
     * @param element the Property that the Element will be based on
     * @param compositor the sequence/choice/all that the Element will be added to
     * @param schema the schema currently being built
     */
    private void addElementToSchema(Element element, String elementURI, boolean isPositional, TypeDefParticle compositor, Schema schema, String packageName) {
        String lookupNamespace = schema.getTargetNamespace();
        if (lookupNamespace == null) {
            lookupNamespace = EMPTY_STRING;
        }
        NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(lookupNamespace, packageName);
        boolean isElementFormQualified = false;
        if (namespaceInfo != null) {
            isElementFormQualified = namespaceInfo.isElementFormQualified();
        }
        boolean addRef = shouldAddRefAndSetForm(element, elementURI, lookupNamespace, isElementFormQualified, true);
        if(addRef){
            addElementRefToSchema(schema, compositor, element, elementURI);
        } else {
            // for positional mappings we could have multiple elements with same name; check before adding
            if (elementExistsInParticle(element.getName(), element.getRef(), compositor) == null) {
                if (isPositional) {
                    element.setMaxOccurs(Occurs.UNBOUNDED);
                }
                compositor.addElement(element);
            }
        }
    }

    /**
     * Convenience method that processes the XmlJoinNodes for a given Property and adds the
     * appropriate components to the schema.
     *
     * @param property the Property contianing one or more XmlJoinNode entries
     * @param compositor the sequence/choice/all that will be added to
     * @param schema the schema currently being built
     * @param type the complex type currently being built
     */
    private void addXmlJoinNodesToSchema(Property property, TypeDefParticle compositor, Schema schema, ComplexType type) {
        for (XmlJoinNode xmlJoinNode : property.getXmlJoinNodes().getXmlJoinNode()) {
            // create the XPathFragment(s) for the path
            Field xfld = new XMLField(xmlJoinNode.getXmlPath());
            xfld.setNamespaceResolver(schema.getNamespaceResolver());
            xfld.initialize();

            // build the schema components for the xml-path
            AddToSchemaResult asr = buildSchemaComponentsForXPath(xfld.getXPathFragment(), new AddToSchemaResult(compositor, schema), false, property);

            // process the last fragment
            TypeDefParticle currentParticle = asr.particle;
            Schema currentSchema = asr.schema;
            if (currentParticle.getOwner() instanceof ComplexType) {
                type = ((ComplexType) currentParticle.getOwner());
            }
            // get a QName for the last part of the xpath - this will be used as the
            // attribute/element name, and also to figure out if a ref is required
            QName schemaName;
            XPathFragment frag = xfld.getLastXPathFragment();
            boolean isAttribute = xmlJoinNode.getXmlPath().contains(ATT);
            // for non-attributes, the last fragment may be 'text()'
            if (!isAttribute) {
                if (frag.nameIsText()) {
                    frag = xfld.getXPathFragment();
                    while (frag.getNextFragment() != null && !frag.getNextFragment().nameIsText()) {
                        frag = frag.getNextFragment();
                    }
                }
            }
            schemaName = new QName(frag.getNamespaceURI(), frag.getLocalName());

            // handle Element/Attribute
            if (isAttribute) {
                addAttributeToSchema(buildAttribute(schemaName, Constants.SCHEMA_PREFIX + COLON + Constants.ANY_SIMPLE_TYPE), schemaName, currentSchema, type);
            } else {
                addElementToSchema(buildElement(schemaName.getLocalPart(), Constants.SCHEMA_PREFIX + COLON + Constants.ANY_SIMPLE_TYPE, currentParticle instanceof All), schemaName.getNamespaceURI(), false, currentParticle, currentSchema, null);
            }
        }
    }

    /**
     * Return the type name for an Element based on a given property.
     *
     * @param property the Property that the type name will be based on
     * @param schema the schema currently being built
     * @param javaClass the given Property's 'actual' type
     * @param sc the element being generated for the given Property
     * @return a type name based on the given Property, or null if not obtainable
     */
    private String getTypeNameForComponent(Property property, Schema schema, JavaClass javaClass, SimpleComponent sc, boolean isElement) {
        String typeName = null;
        if (property.isXmlId()) {
            // handle user-set schema-type
            if (property.getSchemaType() != null) {
                typeName = getTypeName(property, property.getActualType(), schema);
            } else {
                // default to xsd:ID
                typeName = Constants.SCHEMA_PREFIX + COLON + ID;
            }
        } else if (property.isXmlIdRef()) {
            typeName = Constants.SCHEMA_PREFIX + COLON + IDREF;
        } else {
            TypeInfo info = typeInfo.get(javaClass.getQualifiedName());
            if (info != null) {
                if (info.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(isElement && info.hasRootElement() && info.getXmlRootElement().getName().equals(sc.getName())){
                        String refName = info.getXmlRootElement().getName();
                        (sc).setRef(refName);
                    }else{
                        if (isElement && info.isComplexType()) {
                            ((Element)sc).setComplexType(info.getComplexType());
                        } else {
                            sc.setSimpleType(info.getSimpleType());
                        }
                    }
                } else {
                    // check to see if we need to add an import
                    if (addImportIfRequired(schema, info.getSchema(), info.getClassNamespace())) {
                        String prefix = schema.getNamespaceResolver().resolveNamespaceURI(info.getClassNamespace());
                        if (prefix != null && (!typeName.equals(EMPTY_STRING))) {
                            typeName = prefix + COLON + typeName;
                        }
                    }
                }
            } else if (!property.isMap()) {
                typeName = getTypeName(property, javaClass, schema);
            }
            // may need to qualify the type
            if (typeName != null && !typeName.contains(COLON)) {
                String prefix;
                if (info.getClassNamespace().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) {
                    prefix = Constants.SCHEMA_PREFIX;
                } else {
                    prefix = getPrefixForNamespace(schema, info.getClassNamespace());
                }
                if (prefix != null) {
                    typeName = prefix + COLON + typeName;
                }
            }
        }
        return typeName;
    }
    private static String introduceShorthands(String regex) {
        return RegexMutator.mutate(regex);
    }

    /**
     * Maintains compatibility between Java Pattern Regex and XML Schema regex.
     * Replaces Java regexes with their respective XML Regex Shorthands, where applicable.
     * <p>
     * Recognized are Java regexes for the following XML Shorthands, and their negations:
     * <blockquote><pre>
     * \i - Matches any character that may be the first character of an XML name.
     *      "[_:A-Za-z]"
     * \c - Matches any character that may occur after the first character in an XML name.
     *      "[-.0-9:A-Z_a-z]"
     * \d - All digits.
     *      "\\p{Nd}"
     * \w - Word character.
     *      "[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]"
     * \s - Whitespace character.
     *      "[\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]"
     * \b, \B - Boundary definitions.
     *      "(?:(?<=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])
     *      (?![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])|
     *      (?<![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])
     *      (?=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]))"
     *      "(?:(?<=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])
     *      (?=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])|
     *      (?<![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])
     *      (?![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]))"
     * \h - Horizontal whitespace character - Java does not support, changed in Java 8 though.
     *      "[\\u0009\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]"
     * \v - Vertical whitespace character - Java translates the shorthand to \cK only, meaning changed in Java 8 though.
     *      "[\\u000A-\\u000D\\u0085\\u2028\\u2029]"
     * \X - Extended grapheme cluster.
     *      "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5
     *      \\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*(
     *      (?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01
     *      \\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}
     *      \\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\
     *      u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc
     *      }\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))"
     * \R - Carriage return.
     *      "(?:(?>\\u000D\\u000A)|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029])"
     * </pre></blockquote>
     *
     * CAUTION - ORDER SENSITIVE: Longer patterns should come first, because they may contain one of the shorter pattern.
     * <p>
     * Changes to this class should also be reflected in the opposite {@link org.eclipse.persistence.jaxb.plugins.BeanValidationPlugin.RegexMutator RegexMutator} class within XJC BeanValidation Plugin.
     *
     * @see <a href="http://stackoverflow.com/questions/4304928/unicode-equivalents-for-w-and-b-in-java-regular-expressions"/>tchrist's work</a>
     * @see <a href="http://www.regular-expressions.info/shorthand.html#xml">Special shorthands in XML Schema.</a>
     */
    private static final class RegexMutator {
        private static final Map<Pattern, String> shorthandReplacements = new LinkedHashMap<Pattern, String>(32) {{
            put(Pattern.compile("[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]" , Pattern.LITERAL),"\\\\i");
            put(Pattern.compile("[^:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]" , Pattern.LITERAL),"\\\\I");
            put(Pattern.compile("[-.0-9:A-Z_a-z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u203F\\u2040\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]" , Pattern.LITERAL),"\\\\c");
            put(Pattern.compile("[^-.0-9:A-Z_a-z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u203F\\u2040\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD]" , Pattern.LITERAL),"\\\\C");
            put(Pattern.compile("[\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]" , Pattern.LITERAL),"\\\\s");
            put(Pattern.compile("[^\\u0009-\\u000D\\u0020\\u0085\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]" , Pattern.LITERAL),"\\\\S");
            put(Pattern.compile("[\\u000A-\\u000D\\u0085\\u2028\\u2029]" , Pattern.LITERAL),"\\\\v");
            put(Pattern.compile("[^\\u000A-\\u000D\\u0085\\u2028\\u2029]" , Pattern.LITERAL),"\\\\V");
            put(Pattern.compile("[\\u0009\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u202F\\u205F\\u3000]" , Pattern.LITERAL),"\\\\h");
            put(Pattern.compile("[^\\u0009\\u0020\\u00A0\\u1680\\u180E\\u2000\\u2001-\\u200A\\u202F\\u205F\\u3000]" , Pattern.LITERAL),"\\\\H");
            put(Pattern.compile("[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]" , Pattern.LITERAL),"\\\\w");
            put(Pattern.compile("[^\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]" , Pattern.LITERAL),"\\\\W");
            put(Pattern.compile("(?:(?<=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])(?![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])|(?<![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])(?=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]))", Pattern.LITERAL),"\\\\b");
            put(Pattern.compile("(?:(?<=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])(?=[\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])|(?<![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]])(?![\\pL\\pM\\p{Nd}\\p{Nl}\\p{Pc}[\\p{InEnclosedAlphanumerics}&&\\p{So}]]))", Pattern.LITERAL),"\\\\B");
            put(Pattern.compile("\\p{Nd}" , Pattern.LITERAL),"\\\\d");
            put(Pattern.compile("\\P{Nd}" , Pattern.LITERAL),"\\\\D");
            put(Pattern.compile("(?:(?>\\u000D\\u000A)|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029])" , Pattern.LITERAL),"\\\\R");
            put(Pattern.compile("(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))" , Pattern.LITERAL),"\\\\X");
            put(Pattern.compile("[_:A-Za-z]" , Pattern.LITERAL),"\\\\i"); // ascii only
            put(Pattern.compile("[^:A-Z_a-z]" , Pattern.LITERAL),"\\\\I"); // ascii only
            put(Pattern.compile("[-.0-9:A-Z_a-z]" , Pattern.LITERAL),"\\\\c"); // ascii only
            put(Pattern.compile("[^-.0-9:A-Z_a-z]" , Pattern.LITERAL),"\\\\C"); // ascii only
        }};

        private RegexMutator() {
        }

        /**
         * @param input Java regex
         * @return XML regex
         */
        private static String mutate(String input){
            for (Map.Entry<Pattern, String> entry : shorthandReplacements.entrySet()) {
                Matcher m = entry.getKey().matcher(input);
                input = m.replaceAll(entry.getValue());
            }
            return input;
        }
    }
}
