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

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;

import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.namespace.QName;

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

import org.eclipse.persistence.internal.oxm.schema.model.*;
import org.eclipse.persistence.oxm.XMLConstants;

/**
 * INTERNAL:
 * <p><b>Purpose:</b>To generate Schema objects based on a map of TypeInfo objects, and some 
 * additional information gathered by the AnnotationsProcessing phase.
 * <p><b>Responsibilities:</b><ul>
 * <li>Create and maintain a collection of Schema objects based on the provided TypeInfo objects</li>
 * <li>Add additional global elements to the schema based on an optional map (for WS integration)</li>
 * <li>Should create a schema for each namespace encountered during generation.</li>
 * </ul>
 * <p>This class is used by the Generator to handle the generation of Schemas. The
 * Generator passes in a map of TypeInfo objects, generated by the Annotations processor.
 * The generated Schemas are stored in a map of keyed on Target Namespace.
 * @see org.eclipse.persistence.jaxb.compiler.TypeInfo
 * @see org.eclipse.persistence.jaxb.compiler.AnnotationsProcessor
 * @see org.eclipse.persistence.jaxb.compiler.Generator
 * @since Oracle TopLink 11.1.1.0.0
 * @author mmacivor
 */
public class SchemaGenerator {
    private HashMap<String, Schema> schemaForNamespace;
    private Schema schema;
    private int schemaCount;
    private Helper helper;
    private HashMap<String, TypeInfo> typeInfo;
    private HashMap<String, NamespaceInfo> packageToNamespaceMappings;
    private HashMap<String, SchemaTypeInfo> schemaTypeInfo;
    private HashMap<String, QName> userDefinedSchemaTypes;
    
    public SchemaGenerator(Helper helper) {
        this.helper = helper;
    }
    
    public Schema generateSchema(ArrayList<JavaClass> typeInfoClasses, HashMap<String, TypeInfo> typeInfo, HashMap<String, QName> userDefinedSchemaTypes, HashMap<String, NamespaceInfo> packageToNamespaceMappings, HashMap<QName, ElementDeclaration> additionalGlobalElements) {
        this.typeInfo = typeInfo;
        this.userDefinedSchemaTypes = userDefinedSchemaTypes;
        this.packageToNamespaceMappings = packageToNamespaceMappings;
        this.schemaCount = 0;
        this.schemaTypeInfo = new HashMap<String, SchemaTypeInfo>(typeInfo.size());
        
        for (JavaClass javaClass : typeInfoClasses) {
            addSchemaComponents(javaClass);
        }
        populateSchemaTypes();
        if (additionalGlobalElements != null) {
            addGlobalElements(additionalGlobalElements);
        }
        return schema;
    }
    
    public void addSchemaComponents(JavaClass myClass) {
        // first check for type
        String myClassName = myClass.getQualifiedName();
        Element rootElement = null;
        TypeInfo  info = (TypeInfo)typeInfo.get(myClassName);
        SchemaTypeInfo schemaTypeInfo = new SchemaTypeInfo();
        schemaTypeInfo.setSchemaTypeName(new QName(info.getClassNamespace(), info.getSchemaTypeName()));
        this.schemaTypeInfo.put(myClass.getQualifiedName(), schemaTypeInfo);
        NamespaceInfo namespaceInfo = this.packageToNamespaceMappings.get(myClass.getPackage().getQualifiedName());
        Schema schema = getSchemaForNamespace(info.getClassNamespace());
        info.setSchema(schema);

        String typeName = info.getSchemaTypeName();
        String[] propOrder = info.getPropOrder();
        String pfx = "";
        
        Property valueField = null;
        if (helper.isAnnotationPresent(myClass, XmlRootElement.class)) {
            //Create the root element and add it to the schema
            XmlRootElement rootElemAnnotation = (XmlRootElement) helper.getAnnotation(myClass, XmlRootElement.class);
            rootElement = new Element();
            String elementName = rootElemAnnotation.name();
            if (elementName.equals("##default") || elementName.equals("")) {
                if (myClassName.indexOf("$") != -1) {
                    elementName = Introspector.decapitalize(myClassName.substring(myClassName.lastIndexOf('$') + 1));
                } else {
                    elementName = Introspector.decapitalize(myClassName.substring(myClassName.lastIndexOf('.') + 1));                    
                }
                
                // TODO - remove this TCK hack...
                if (elementName.length() >= 3) {
                    int idx = elementName.length()-1;
                    char ch = elementName.charAt(idx-1);
                    if (Character.isDigit(ch)) {
                        char lastCh = Character.toUpperCase(elementName.charAt(idx));
                        elementName = elementName.substring(0, idx) + lastCh;
                    }
                }
            }
            rootElement.setName(elementName);
            String rootNamespace = rootElemAnnotation.namespace();
            if (rootNamespace.equals("##default")) {
                getSchemaForNamespace(namespaceInfo.getNamespace()).addTopLevelElement(rootElement);
                schemaTypeInfo.getGlobalElementDeclarations().add(new QName(namespaceInfo.getNamespace(), rootNamespace));
                rootNamespace = namespaceInfo.getNamespace();
            } else {
                getSchemaForNamespace(rootNamespace).addTopLevelElement(rootElement);
                schemaTypeInfo.getGlobalElementDeclarations().add(new QName(rootNamespace, elementName));
            }

			// handle root-level imports/includes [schema = the type's schema]
            Schema rootSchema = getSchemaForNamespace(rootNamespace);
            if (schema != rootSchema) {
                Import schemaImport = new Import();
                schemaImport.setNamespace(schema.getTargetNamespace());
                schemaImport.setSchemaLocation(schema.getName());                
                rootSchema.getImports().add(schemaImport);
            }
            // setup a prefix, if necessary
            if (!info.getClassNamespace().equals("")) {
                pfx = getPrefixForNamespace(info.getClassNamespace(), rootSchema.getNamespaceResolver());
                if (pfx == null) {
                    pfx = rootSchema.getNamespaceResolver().generatePrefix();
                    rootSchema.getNamespaceResolver().put(pfx, info.getClassNamespace());
                }
                pfx += ":";
            }
        }
       
        ArrayList<String> propertyNames = info.getPropertyNames();
        if (info.isEnumerationType() || (propertyNames.size() == 1 && helper.isAnnotationPresent(info.getProperties().get(propertyNames.get(0)).getElement(), XmlValue.class))) {
            SimpleType type = new SimpleType();
            //simple type case, we just need the name and namespace info
            //TODO: add namespace support
            if (typeName.equals("")) {
                //In this case, it should be a type under
                //A root elem or locally defined whenever used
                if(rootElement != null) {
                    rootElement.setSimpleType(type);
                }
            } else {
                type.setName(typeName);
                schema.addTopLevelSimpleTypes(type);
                if (rootElement != null) {
                    rootElement.setType(pfx+type.getName());
                }
            }
            //Figure out schema type and set it as Restriction
            QName restrictionType = null;
            Restriction restriction = new Restriction();
            if (info.isEnumerationType()) {
                restrictionType = ((EnumTypeInfo)info).getRestrictionBase();
                restriction.setEnumerationFacets(this.getEnumerationFacetsFor((EnumTypeInfo)info));
                restriction.setBaseType(XMLConstants.SCHEMA_PREFIX + ":" + restrictionType.getLocalPart());
                type.setRestriction(restriction);
            } else {
                valueField = info.getProperties().get(propertyNames.get(0));
                QName baseType = getSchemaTypeFor(valueField.getType());
                if (helper.isAnnotationPresent(valueField.getElement(), XmlList.class)) {
                    //generate a list instead of a restriction
                    List list = new List();
                    list.setItemType(XMLConstants.SCHEMA_PREFIX + ":" + baseType.getLocalPart());
                    type.setList(list);
                } else {
                    if (helper.isAnnotationPresent(valueField.getElement(), XmlSchemaType.class)) {
                        XmlSchemaType schemaType = (XmlSchemaType) helper.getAnnotation(valueField.getElement(), XmlSchemaType.class);
                        baseType = new QName(XMLConstants.SCHEMA_INSTANCE_URL, schemaType.name());
                    }
                    restriction.setBaseType(XMLConstants.SCHEMA_PREFIX + ":" + baseType.getLocalPart());
                    type.setRestriction(restriction);
                }
            }
            info.setSimpleType(type);
        } else if ((valueField = this.getXmlValueFieldForSimpleContent(info.getPropertyList())) != null) {
            ComplexType type = new ComplexType();
            SimpleContent content = new SimpleContent();
            if (typeName.equals("")) {
                if(rootElement != null) {
                    rootElement.setComplexType(type);
                }
                info.setComplexType(type);
            } else {
                type.setName(typeName);
                schema.addTopLevelComplexTypes(type);
                if(rootElement != null) {
                    rootElement.setType(pfx+type.getName());
                }
            }
            QName extensionType = getSchemaTypeFor(valueField.getType());
            if (helper.isAnnotationPresent(valueField.getElement(), XmlSchemaType.class)) {
                XmlSchemaType schemaType = (XmlSchemaType) helper.getAnnotation(valueField.getElement(), XmlSchemaType.class);
                extensionType = new QName(XMLConstants.SCHEMA_INSTANCE_URL, schemaType.name());
            }
            Extension extension = new Extension();
            extension.setBaseType(XMLConstants.SCHEMA_PREFIX + ":" + extensionType.getLocalPart());
            content.setExtension(extension);
            type.setSimpleContent(content);
            info.setComplexType(type);
            info.setPropOrder(propOrder);
        }  else {
            ComplexType type = new ComplexType();
            JavaClass superClass = (JavaClass) myClass.getSuperclass();
            TypeInfo parentTypeInfo = this.typeInfo.get(superClass.getQualifiedName());
            Extension extension = null;
            if (parentTypeInfo != null) {
                extension = new Extension();
                // may need to qualify the type
                String parentPrefix = getPrefixForNamespace(parentTypeInfo.getClassNamespace(), namespaceInfo.getNamespaceResolver()); 
                if (parentPrefix != null) {
                    extension.setBaseType(parentPrefix + ":" + parentTypeInfo.getSchemaTypeName());
                } else {
                    extension.setBaseType(parentTypeInfo.getSchemaTypeName());
                }
                ComplexContent content = new ComplexContent();
                content.setExtension(extension);
                type.setComplexContent(content);
            }
            TypeDefParticle compositor = null;
            if (propOrder.length == 0) {
                // TODO: needed to hack for TCK - 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()) {
            		//needs to be a sequence
            		compositor = new Sequence();
            		if(extension != null) {
            			extension.setSequence((Sequence)compositor);
            		} else {
            			type.setSequence((Sequence)compositor);
            		}
            	} else if (extension != null) {
                    //compositor = new Sequence();
                    //extension.setSequence((Sequence)compositor);
                    compositor = new All();
                    extension.setAll((All)compositor);
                } else {
                    compositor = new All();
                    type.setAll((All)compositor);
                }
            } else {
                compositor = new Sequence();
                if (extension != null) {
                    extension.setSequence((Sequence)compositor);
                } else {
                    type.setSequence((Sequence)compositor);
                }
            }
            if (typeName.equals("")) {
                if (rootElement != null) {
                   rootElement.setComplexType(type);
                }
                info.setComplexType(type);
                info.setCompositor(compositor);
            } else {
                type.setName(typeName);
                if(rootElement != null) {
                    rootElement.setType(pfx+type.getName());
                }
                schema.addTopLevelComplexTypes(type);
                info.setComplexType(type);
                info.setCompositor(compositor);
            }
            info.setPropOrder(propOrder);            
        }
    }
    
    public void addToSchemaType(ArrayList<Property> properties, TypeDefParticle compositor, ComplexType type, Schema schema) {
        for (Property next : properties) {
            // TODO:  we seem to get a null property on occasion
            // need to look into this...
            if (next == null) { continue; }
            
            TypeDefParticle parentCompositor = compositor;
            boolean isChoice = (parentCompositor instanceof Choice);
            ComplexType parentType = type;
            if (!helper.isAnnotationPresent(next.getElement(), XmlTransient.class)) {
                // deal with the XmlElementWrapper case
                if (!isChoice && helper.isAnnotationPresent(next.getElement(), XmlElementWrapper.class)) {
                    XmlElementWrapper wrapper = (XmlElementWrapper) helper.getAnnotation(next.getElement(), XmlElementWrapper.class);
                    Element wrapperElement = new Element();
                    wrapperElement.setName(wrapper.name());
                    wrapperElement.setMinOccurs("0");
                    compositor.addElement(wrapperElement);
                    ComplexType wrapperType = new ComplexType();
                    Sequence wrapperSequence = new Sequence();
                    wrapperType.setSequence(wrapperSequence);
                    wrapperElement.setComplexType(wrapperType);
                    parentType = wrapperType;
                    parentCompositor = wrapperSequence;
                }
                if (helper.isAnnotationPresent(next.getElement(), XmlAttribute.class)) {
                    Attribute attribute = new Attribute();
                    QName attributeName = next.getSchemaName(); 
                    attribute.setName(attributeName.getLocalPart());
                    //Check to see if it's a collection. 
                    //Should assume XmlList on any collection?
                    JavaClass javaType = next.getType();
                    if (next.getGenericType() != null) {
                        javaType = (JavaClass) next.getGenericType();
                    }
                    String typeName = null;
                    TypeInfo info = (TypeInfo)typeInfo.get(next.getType().getQualifiedName());
                    if (info != null) {
                        if (!info.isComplexType()) {
                            typeName = info.getSimpleType().getName();
                        }
                    } else {
                    	if (helper.isAnnotationPresent(next.getElement(), XmlID.class)) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":ID";
                    	} else if (helper.isAnnotationPresent(next.getElement(), XmlIDREF.class)) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":IDREF";
                    	} else {
	                        QName schemaType = next.getSchemaType();
	                        if (schemaType == null) {
	                            schemaType = getSchemaTypeFor(javaType);
	                        }
	                        if (schemaType != null) {
	                            typeName = XMLConstants.SCHEMA_PREFIX + ":" + schemaType.getLocalPart();
	                        } else {
	                            typeName = XMLConstants.SCHEMA_PREFIX + ":anySimpleType";
	                        }
                    	}
                    }
                    if (isCollectionType(next)) {
                        // assume XmlList for an attribute collection
                        SimpleType localType = new SimpleType();
                        org.eclipse.persistence.internal.oxm.schema.model.List list = new org.eclipse.persistence.internal.oxm.schema.model.List();
                        list.setItemType(typeName);
                        localType.setList(list);
                        attribute.setSimpleType(localType);
                    } else {
                        // may need to qualify the type
                        if (typeName != null && !typeName.contains(":")) {
                            if (info.getSchema() == schema) {
                                String prefix = getPrefixForNamespace(schema.getTargetNamespace(), schema.getNamespaceResolver());
                                if (prefix != null) {
                                    typeName = prefix + ":" + typeName;
                                }
                            }
                        }
                        attribute.setType(typeName);
                    }
                    if (!attributeName.getNamespaceURI().equals("")) {
                        Schema attributeSchema = this.getSchemaForNamespace(attributeName.getNamespaceURI());
                        if(attributeSchema.getTopLevelAttributes().get(attribute.getName()) == null) {
                            //don't overwrite existing global elements and attributes.
                            attributeSchema.getTopLevelAttributes().put(attribute.getName(), attribute);
                        }
                        if(!importExists(schema, attributeSchema.getName())){                        
                            Import schemaImport = new Import();
                            schemaImport.setNamespace(attributeSchema.getTargetNamespace());
                            schemaImport.setSchemaLocation(attributeSchema.getName());                            
                            schema.getImports().add(schemaImport);
                            schema.getNamespaceResolver().put(schema.getNamespaceResolver().generatePrefix(), attributeSchema.getTargetNamespace());
                        }
                        Attribute reference = new Attribute();
                        //add an import here
                        String prefix = getPrefixForNamespace(attributeSchema.getTargetNamespace(), schema.getNamespaceResolver());
                        if (prefix == null) {
                            reference.setRef(attribute.getName());
                        } else {
                            reference.setRef(prefix + ":" + attribute.getName());
                        }
                        if(parentType.getSimpleContent() != null) {
                            parentType.getSimpleContent().getExtension().getOrderedAttributes().add(reference);
                        } else {
                            parentType.getOrderedAttributes().add(reference);
                        }
                    } else {
                        if(parentType.getSimpleContent() != null) {
                            parentType.getSimpleContent().getExtension().getOrderedAttributes().add(attribute);
                        } else {
                            parentType.getOrderedAttributes().add(attribute);
                        }
                    }
                } else if (helper.isAnnotationPresent(next.getElement(), XmlAnyAttribute.class)) {
                    AnyAttribute anyAttribute = new AnyAttribute();
                    anyAttribute.setProcessContents(AnyAttribute.LAX);
                    if (type.getSimpleContent() != null) {
                        SimpleContent content = type.getSimpleContent();
                        content.getRestriction().setAnyAttribute(anyAttribute);
                    } else {
                        type.setAnyAttribute(anyAttribute);
                    }
                } else if(next.isChoice()) {
                    Choice choice = new Choice();
                    ArrayList<Property> choiceProperties = (ArrayList<Property>)((ChoiceProperty)next).getChoiceProperties();
                    addToSchemaType(choiceProperties, choice, parentType, schema);
                    if(parentCompositor instanceof Sequence) {
                        ((Sequence)parentCompositor).addChoice(choice);
                    } else if(parentCompositor instanceof Choice) {
                        ((Choice)parentCompositor).addChoice(choice);
                    }
                } else if(next.isAny()) {
                	Any any = new Any();
                	AnyProperty anyProp = (AnyProperty)next;
                	if(anyProp.isLax()) {
                		any.setProcessContents("lax");
                	}
                    if(parentCompositor instanceof Sequence) {
                        ((Sequence)parentCompositor).addAny(any);
                    } else if(parentCompositor instanceof Choice) {
                        ((Choice)parentCompositor).addAny(any);
                    }
                	
                } else if(next.isReference()) {
                	ReferenceProperty refProp = (ReferenceProperty)next;
                	ArrayList<ElementDeclaration> referencedElements = refProp.getReferencedElements();
                	if(referencedElements.size() == 1) {
                		//if only a single reference, just add the element.
                    	Element element = new Element();
                		ElementDeclaration decl = referencedElements.get(0);
                		String localName = decl.getElementName().getLocalPart();
                		Schema referencedSchema = this.getSchemaForNamespace(decl.getElementName().getNamespaceURI());
                		if(referencedSchema != schema) {
                			//add import and namespace prefix if requried
                            if(!importExists(schema, referencedSchema.getName())){                            
                                Import schemaImport = new Import();
                                schemaImport.setSchemaLocation(referencedSchema.getName());
                                schemaImport.setNamespace(referencedSchema.getTargetNamespace());                                
                                schema.getImports().add(schemaImport);
                                if (schemaImport.getNamespace() != null) {
                                    schema.getNamespaceResolver().put(schema.getNamespaceResolver().generatePrefix(), schemaImport.getNamespace());
                                }
                            }
                		}
                		String prefix = this.getPrefixForNamespace(decl.getElementName().getNamespaceURI(), schema.getNamespaceResolver());
                		if(prefix == null || prefix.equals("")) {
                			element.setRef(localName);
                		} else {
                			element.setRef(prefix + ":" + localName);
                		}
                		if(isCollectionType(next)) {
                			element.setMaxOccurs("unbounded");
                		}
                		parentCompositor.addElement(element);
                	} else {
                		// otherwise, add a choice of referenced elements.
                		Choice choice = new Choice();
                		if(isCollectionType(next)) {
                			choice.setMaxOccurs("unbounded");
                		}
                		for(ElementDeclaration elementDecl:referencedElements) {
                			Element element = new Element();
                    		String localName = elementDecl.getElementName().getLocalPart();
                    		Schema referencedSchema = this.getSchemaForNamespace(elementDecl.getElementName().getNamespaceURI());
                    		if(referencedSchema != schema) {
                    			//add import and namespace prefix if requried
                                if(!importExists(schema, referencedSchema.getName())){                            
                                    Import schemaImport = new Import();
                                    schemaImport.setSchemaLocation(referencedSchema.getName());
                                    schemaImport.setNamespace(referencedSchema.getTargetNamespace());                                
                                    schema.getImports().add(schemaImport);
                                    if (schemaImport.getNamespace() != null) {
                                        schema.getNamespaceResolver().put(schema.getNamespaceResolver().generatePrefix(), schemaImport.getNamespace());
                                    }
                                }
                    		}
                    		String prefix = this.getPrefixForNamespace(elementDecl.getElementName().getNamespaceURI(), schema.getNamespaceResolver());
                    		if(prefix == null || prefix.equals("")) {
                    			element.setRef(localName);
                    		} else {
                    			element.setRef(prefix + ":" + localName);
                    		}
                    		choice.addElement(element);
                		}
                        if(parentCompositor instanceof Sequence) {
                            ((Sequence)parentCompositor).addChoice(choice);
                        } else if(parentCompositor instanceof Choice) {
                            ((Choice)parentCompositor).addChoice(choice);
                        }
                	}
                } else if (!helper.isAnnotationPresent(next.getElement(), XmlValue.class)) {
                    Element element = new Element();
                    // Set minOccurs based on the 'required' flag
                    element.setMinOccurs(next.isRequired() ? "1" : "0");
                    QName elementName = next.getSchemaName();
                    JavaClass javaType = next.getType();
                    boolean isCollectionType = isCollectionType(next);
                    if (isCollectionType) {
                        JavaClass gType = next.getGenericType();
                        if (gType != null && gType.hasActualTypeArguments()) {
                            Object[] params = gType.getActualTypeArguments().toArray();
                            javaType = (JavaClass) params[0];
                        }
                    }
                    element.setName(elementName.getLocalPart());
                    
                    TypeInfo info = (TypeInfo)typeInfo.get(javaType.getQualifiedName());
                    String typeName = null;
                    boolean isComplexType = false;
                    if (info != null) {
                    	if (helper.isAnnotationPresent(next.getElement(), XmlID.class)) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":ID";
                    	} else if (helper.isAnnotationPresent(next.getElement(), XmlIDREF.class)) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":IDREF";
                    	} else {
	                        isComplexType = info.isComplexType();
	                        if (info.isComplexType()) {
	                            typeName = info.getComplexType().getName();
	                        } else {
	                            typeName = info.getSimpleType().getName();
	                        }
                    	}
                        
                        if(typeName == null) {
                            //need to add complex-type locally, or reference global element
                            if(!info.hasRootElement()) {
                                if(info.isComplexType()) {
                                    element.setComplexType(info.getComplexType());
                                } else {
                                    element.setSimpleType(info.getSimpleType());
                                }
                            }
                        }
                        // check to see if we need to add an import
                        if (info.getSchema() != schema) {
                            if(!importExists(schema, info.getSchema().getName())){                            
                                Import schemaImport = new Import();
                                schemaImport.setSchemaLocation(info.getSchema().getName());
                                schemaImport.setNamespace(info.getSchema().getTargetNamespace());                                
                                schema.getImports().add(schemaImport);
                                if (schemaImport.getNamespace() != null) {
                                    schema.getNamespaceResolver().put(schema.getNamespaceResolver().generatePrefix(), schemaImport.getNamespace());
                                }
                                // qualify the type name
                                String prefix = getPrefixForNamespace(info.getSchema().getTargetNamespace(), schema.getNamespaceResolver());
                                if (prefix != null && !typeName.equals("")) {
                                    typeName = prefix + ":" + typeName;
                                }
                            }
                        }
                    } else {
                        QName schemaType = next.getSchemaType();
                        if (schemaType == null) {
                            schemaType = getSchemaTypeFor(javaType);
                        }
                        if (schemaType != null) {
                            typeName = XMLConstants.SCHEMA_PREFIX + ":" + schemaType.getLocalPart();
                        }
                    }
                    
                    // may need to qualify the type
                    if (typeName != null && !typeName.contains(":")) {
                        if (info.getSchema() == schema) {
                            String prefix = getPrefixForNamespace(schema.getTargetNamespace(), schema.getNamespaceResolver());
                            if (prefix != null) {
                                typeName = prefix + ":" + typeName;
                            }
                        }
                    }
                    
                    if (isCollectionType) {
                        if (helper.isAnnotationPresent(next.getElement(), XmlList.class)) {
                            if (isComplexType) {
                                //TODO: Error case probably
                            }
                            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("unbounded");
                            element.setType(typeName);
                        }
                    } else {
                        element.setType(typeName);
                    }
                    if (!elementName.getNamespaceURI().equals("")) {
                        Element reference = new Element();
                        reference.setMinOccurs(element.getMinOccurs());
                        reference.setMaxOccurs(element.getMaxOccurs());
                        Schema attributeSchema = this.getSchemaForNamespace(elementName.getNamespaceURI());
                        if(attributeSchema.getTopLevelElements().get(element.getName()) == null) {
                            // reset min/max occurs as they aren't applicable for global elements
                            element.setMinOccurs(null);
                            element.setMaxOccurs(null);
                            //don't overwrite global elements. May have been defined by a type.
                            attributeSchema.getTopLevelElements().put(element.getName(), element);
                        }
                        if (attributeSchema != schema && (!importExists(schema, attributeSchema.getName()))){ 
                            Import schemaImport = new Import();
                            schemaImport.setNamespace(attributeSchema.getTargetNamespace());
                            schemaImport.setSchemaLocation(attributeSchema.getName());                            
                            schema.getImports().add(schemaImport);
                            schema.getNamespaceResolver().put(schema.getNamespaceResolver().generatePrefix(), attributeSchema.getTargetNamespace());
                        }
                        //add an import here
                        String prefix = getPrefixForNamespace(attributeSchema.getTargetNamespace(), schema.getNamespaceResolver());
                        if (prefix == null) {
                            reference.setRef(element.getName());
                        } else {
                            reference.setRef(prefix + ":" + element.getName());
                        }
                        parentCompositor.addElement(reference);
                    } else {
                        parentCompositor.addElement(element);
                    }
                }
            }
        }
    }
    
    public QName getSchemaTypeFor(JavaClass javaClass) {
        // check user defined types first
        QName schemaType = (QName) userDefinedSchemaTypes.get(javaClass.getQualifiedName());
        if (schemaType == null) {
            schemaType = (QName) helper.getXMLToJavaTypeMap().get(javaClass.getRawName());
        }
        if (schemaType == null) {
            return XMLConstants.ANY_SIMPLE_TYPE_QNAME;
        }
        return schemaType;
    }
    
    public void populateSchemaTypes() {
        Iterator<String> classNames = typeInfo.keySet().iterator();
        while (classNames.hasNext()) {
            String javaClassName = classNames.next();
            TypeInfo info = (TypeInfo)typeInfo.get(javaClassName);
            if (info.isComplexType()) {
                ComplexType type = info.getComplexType();
                TypeDefParticle compositor = info.getCompositor();
                String[] propOrder = info.getPropOrder();
                if (propOrder.length == 0 || propOrder[0].equals("")) {
                    propOrder = (String[])info.getPropertyNames().toArray(new String[info.getPropertyNames().size()]);
                }
                ArrayList<Property> properties = new ArrayList(propOrder.length); 
                for (int i = 0; i < propOrder.length; i++) {
                    Property next = info.getProperties().get(propOrder[i]);
                    properties.add(next);
                }
                addToSchemaType(properties, compositor, type, info.getSchema());
            }
        }
    } 
    
    public String getSchemaTypeNameForClassName(String className) {
        String typeName = Introspector.decapitalize(className.substring(className.lastIndexOf('.') + 1));
        return typeName;
    }    
    
    public ArrayList getEnumerationFacetsFor(EnumTypeInfo info) {
        Collection valuesCollection = info.getObjectValuesToFieldValues().values();
        return new ArrayList(valuesCollection);
    }

    public Property getXmlValueFieldForSimpleContent(ArrayList<Property> properties) {
        boolean foundValue = false;
        boolean foundNonAttribute = false;
        Property valueField = null;
        
        for (Property prop : properties) {
            if (helper.isAnnotationPresent(prop.getElement(), XmlValue.class)) {
                foundValue = true;
                valueField = prop;
            } else if (!helper.isAnnotationPresent(prop.getElement(), XmlAttribute.class) && !helper.isAnnotationPresent(prop.getElement(), XmlTransient.class) && !helper.isAnnotationPresent(prop.getElement(), XmlAnyAttribute.class)) {
                foundNonAttribute = true;
            }
        }
        if (foundValue && !foundNonAttribute) {
            return valueField;
        }
        return null;
    }
    
    public boolean isCollectionType(Property field) {
        JavaClass type = field.getType();
        return (helper.getJavaClass(java.util.Collection.class).isAssignableFrom(type) 
                || helper.getJavaClass(java.util.List.class).isAssignableFrom(type) 
                || helper.getJavaClass(java.util.Set.class).isAssignableFrom(type));
    } 
    
    private Schema getSchemaForNamespace(String namespace) {
        if(schemaForNamespace == null) {
            schemaForNamespace = new HashMap<String, Schema>();
        }
        Schema schema = schemaForNamespace.get(namespace);
        if (schema == null) {
            NamespaceInfo namespaceInfo = getNamespaceInfoForNamespace(namespace);
            schema = new Schema();
            schema.setName("schema" + schemaCount + ".xsd");
            schemaCount++;

            if (!namespace.equals("")) {
                schema.setTargetNamespace(namespace);
                String prefix = null;
                if (namespaceInfo != null) {
                    prefix = namespaceInfo.getNamespaceResolver().resolveNamespaceURI(namespace);
                }
                if (prefix == null) {
                    prefix = schema.getNamespaceResolver().generatePrefix();
                }
                schema.getNamespaceResolver().put(prefix, namespace);
            }
            
            if (namespaceInfo != null) {
                schema.setAttributeFormDefault(namespaceInfo.isAttributeFormQualified());
                schema.setElementFormDefault(namespaceInfo.isElementFormQualified());
            }
            schemaForNamespace.put(namespace, schema);
        }
        return schema;
    }
    
    public Collection<Schema> getAllSchemas() {
        if(schemaForNamespace == null) {
            schemaForNamespace = new HashMap<String, Schema>();
        }
        return schemaForNamespace.values();
    }
    
    public NamespaceInfo getNamespaceInfoForNamespace(String namespace) {
        Collection<NamespaceInfo> namespaceInfo = packageToNamespaceMappings.values();
        for (NamespaceInfo info : namespaceInfo) {
            if (info.getNamespace().equals(namespace)) {
                return info;
            }
        }
        return null;
    }
    
    public String getPrefixForNamespace(String URI, org.eclipse.persistence.oxm.NamespaceResolver namespaceResolver) {
        Enumeration keys = namespaceResolver.getPrefixes();
        while (keys.hasMoreElements()) {
            String next = (String)keys.nextElement();
            String nextUri = namespaceResolver.resolveNamespacePrefix(next);
            if (nextUri.equals(URI)) {
                return next;
            }
        }
        return null;
    }
    
    public void addGlobalElements(HashMap<QName, ElementDeclaration> additionalElements) {
        for (QName next : additionalElements.keySet()) {
            String namespaceURI = next.getNamespaceURI();
            Schema targetSchema = getSchemaForNamespace(namespaceURI);
            //TODO: check for existing element in Target Schema first
            Element element = new Element();
            element.setName(next.getLocalPart());

            ElementDeclaration nextElement = additionalElements.get(next);
            JavaClass javaClass = helper.getJavaClass(nextElement.getJavaTypeName());
            
            //First check for built in type
            QName schemaType = (QName) helper.getXMLToJavaTypeMap().get(javaClass.getRawName());
            if (schemaType != null) {
                element.setType(XMLConstants.SCHEMA_PREFIX + ":" + schemaType.getLocalPart());
            } else {
                TypeInfo type = (TypeInfo)this.typeInfo.get(javaClass.getQualifiedName());
                if (type != null) {
                    String typeName = null;
                    if (type.isComplexType()) {
                        typeName = type.getComplexType().getName();
                    } else {
                        typeName = type.getSimpleType().getName();
                    }
                    //check namespace of schemaType
                    if (type.getClassNamespace().equals(namespaceURI)) {
                        //no need to prefix here
                        element.setType(typeName);
                    } else {
                        Schema complexTypeSchema = getSchemaForNamespace(type.getClassNamespace());
                        String complexTypeSchemaNS = complexTypeSchema.getTargetNamespace();
                        if(complexTypeSchemaNS == null) {
                            complexTypeSchemaNS = "";
                        }                        
                        if(!importExists(targetSchema, complexTypeSchema.getName())){
                            Import schemaImport = new Import();
                            schemaImport.setNamespace(complexTypeSchema.getTargetNamespace());
                            schemaImport.setSchemaLocation(complexTypeSchema.getName());                            
                            targetSchema.getImports().add(schemaImport);
                            // Don't need to generate prefix for default namespace
                            if (!complexTypeSchemaNS.equals("")) {
                                targetSchema.getNamespaceResolver().put(targetSchema.getNamespaceResolver().generatePrefix(), complexTypeSchemaNS);
                            }
                        }
                        String prefix = targetSchema.getNamespaceResolver().resolveNamespaceURI(complexTypeSchema.getTargetNamespace());
                        if(prefix != null) {
                        	element.setType(prefix + ":" + typeName);
                        } else {
                        	element.setType(typeName);
                        }
                    }
                }
            }
            if(nextElement.getSubstitutionHead() != null) {
            	String subLocal = nextElement.getSubstitutionHead().getLocalPart();
            	String subNamespace = nextElement.getSubstitutionHead().getNamespaceURI();
            	String prefix = getPrefixForNamespace(subNamespace, targetSchema.getNamespaceResolver());
            	if(prefix == null || prefix.equals("")) {
            		element.setSubstitutionGroup(subLocal);
            	} else {
            		element.setSubstitutionGroup(prefix + ":" + subLocal);
            	}
            }
            targetSchema.addTopLevelElement(element);
            SchemaTypeInfo info = this.schemaTypeInfo.get(javaClass.getQualifiedName());
            if (info == null) {
                //probably for a simple type, not generated by toplink
                info = new SchemaTypeInfo();
                info.setSchemaTypeName(schemaType);
                schemaTypeInfo.put(javaClass.getQualifiedName(), info);
            }
            info.getGlobalElementDeclarations().add(next);
        }
    }
    
    public HashMap<String, SchemaTypeInfo> getSchemaTypeInfo() {
        return this.schemaTypeInfo;
    }
    
    private boolean importExists(Schema schema, String schemaName){
        
        java.util.List imports = schema.getImports();
        for(int i=0;i < imports.size();i++){
          Import nextImport = (Import)imports.get(i);
          if(nextImport.getSchemaLocation() != null && nextImport.getSchemaLocation().equals(schemaName)){
            return true;
          }
        }
        return false;                
    }
}
