/**
 * Copyright (c) 2002-2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.xsd.ecore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;

import org.eclipse.xsd.XSDAnnotation;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDAttributeUse;
import org.eclipse.xsd.XSDAttributeUseCategory;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDComponent;
import org.eclipse.xsd.XSDCompositor;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDConstraint;
import org.eclipse.xsd.XSDDerivationMethod;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDEnumerationFacet;
import org.eclipse.xsd.XSDFacet;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDConstants;
import org.eclipse.xsd.util.XSDParser;


/**
 * This class produces an XSDSchema given an Ecore EPackage.
 * The XSDSchema is an XML schema.
 */
public class EcoreXMLSchemaBuilder extends MapBuilder
{
  public interface QNameMap
  {
    String getName(ENamedElement element);
  }
  public static final String OPTION_TO_ENFORCE_LOWERBOUND = "ENFORCE_LOWERBOUND";
  public static final String OPTION_USE_ENCODED_ATTRIBUTE_STYLE = "USE_ENCODE_ATTRIBUTE_STYLE";

  protected static final String EMF_SCHEMA_URI = "http://org.eclipse.emf/xsd";
  protected static final String EMF_SCHEMA_PREFIX = "emf";
  protected static final String EMF_SCHEMA_NAME = "EMF.xsd";
  protected static final String REFERENCE_TYPE_NAME = "string";

  protected XSDSchema xsdSchema;
  protected XSDSchema emfSchema;
  protected EPackage ePackage;
  protected Map<String, String> ecoreToSchemaName;
  protected Map<EPackage, XSDSchema> ePackageToXSDSchemaMap = new HashMap<EPackage, XSDSchema>();
  protected Map<EPackage, String> ePackageToNsPrefixMap = new HashMap<EPackage, String>();
  protected QNameMap qNameMap;

  protected boolean minimizedXMI;
  protected boolean useEncodedAttributeStyle;
  protected boolean enforceLowerBound;
  protected List<EClass> rootClasses;
  protected XSDParser xsdParser;

  public EcoreXMLSchemaBuilder()
  {
    // Make sure the xsd prefix isn't used by a package.
    //
    ePackageToNsPrefixMap.put(null, "xsd");
  }

  public Collection<EObject> generate(EPackage ePackage)
  {
    return generate(ePackage, null);
  }

  public Collection<EObject> generate(EPackage ePackage, QNameMap qNameMap)
  {
    this.qNameMap = qNameMap;

    addInput(ePackage);

    this.ePackage = ePackage;
    createSchema();
    processEnums();
    processClasses();
    Collection<EObject> result = new ArrayList<EObject>();
    result.add(xsdSchema);
    if (emfSchema != null)
    {
      result.add(emfSchema);
    }
    if (mapper != null)
    {
      result.add(mapper.getRoot());
    }

    // Add model documentation.
    //
    for (Map.Entry<XSDComponent, EModelElement> entry : xsdComponentToEModelElementMap.entrySet())
    {
      addDocumentation(entry.getKey(), entry.getValue());
    }

    return result;
  }

  protected String getUniqueNsPrefix(EPackage ePackage)
  {
    if (ePackageToNsPrefixMap.containsKey(ePackage))
    {
      return ePackageToNsPrefixMap.get(ePackage);
    }
    else
    {
      String nsPrefix = ePackage.getNsPrefix();
      String uniqueNsPrefix = nsPrefix;
      for (int i = 1; ePackageToNsPrefixMap.values().contains(uniqueNsPrefix); ++i)
      {
        uniqueNsPrefix = nsPrefix + "_" + i;
      }
      ePackageToNsPrefixMap.put(ePackage, uniqueNsPrefix);
      return uniqueNsPrefix;
    }
  }

  protected void createSchema()
  {
    xsdSchema = XSDFactory.eINSTANCE.createXSDSchema();
    addOutput(xsdSchema);
    // addInput(xsdSchema);

    xsdSchema.setTargetNamespace(ePackage.getNsURI());
    xsdSchema.setSchemaForSchemaQNamePrefix("xsd");

    Map<String, String> namespaces = xsdSchema.getQNamePrefixToNamespaceMap();
    namespaces.put(getUniqueNsPrefix(ePackage), xsdSchema.getTargetNamespace());
    namespaces.put(xsdSchema.getSchemaForSchemaQNamePrefix(), XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001);

    map(xsdSchema, ePackage);
  }

  protected void createEMFSchema()
  {
    emfSchema = XSDFactory.eINSTANCE.createXSDSchema();

    emfSchema.setTargetNamespace(EMF_SCHEMA_URI);
    emfSchema.setSchemaForSchemaQNamePrefix("xsd");

    Map<String, String> namespaces = emfSchema.getQNamePrefixToNamespaceMap();
    namespaces.put(EMF_SCHEMA_PREFIX, emfSchema.getTargetNamespace());
    namespaces.put(emfSchema.getSchemaForSchemaQNamePrefix(), XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001);

    XSDSimpleTypeDefinition list = XSDFactory.eINSTANCE.createXSDSimpleTypeDefinition();
    list.setName(REFERENCE_TYPE_NAME);

    XSDSimpleTypeDefinition union = XSDFactory.eINSTANCE.createXSDSimpleTypeDefinition();
    union.getMemberTypeDefinitions().add(emfSchema.getSchemaForSchema().resolveSimpleTypeDefinition("IDREF"));
    union.getMemberTypeDefinitions().add(emfSchema.getSchemaForSchema().resolveSimpleTypeDefinition("QName"));
    union.getMemberTypeDefinitions().add(emfSchema.getSchemaForSchema().resolveSimpleTypeDefinition("anyURI"));

    list.setItemTypeDefinition(union);
    list.getContents().add(union);

    emfSchema.getContents().add(list);
  }

  protected void addEMFSchema()
  {
    // Do nothing.
  }

  protected XSDTypeDefinition typeInOtherSchema(EClassifier classifier)
  {
    EPackage typePkg = classifier.getEPackage();
    Map<String, String> namespaces = xsdSchema.getQNamePrefixToNamespaceMap();

    String nsPrefix = getUniqueNsPrefix(typePkg);
    if (namespaces.get(nsPrefix) == null)
    {
      namespaces.put(nsPrefix, typePkg.getNsURI());
      addImport(typePkg.getNsURI(), getName(typePkg) + ".xsd");
      createOtherSchema(typePkg);
    }

    XSDSchema otherXSDSchema = ePackageToXSDSchemaMap.get(typePkg);
    return otherXSDSchema.resolveTypeDefinition(getName(classifier));
  }

  protected void addImport(String namespace, String schemaLocation)
  {
    XSDImport xsdImport = XSDFactory.eINSTANCE.createXSDImport();
    xsdImport.setNamespace(namespace);
    xsdImport.setSchemaLocation(schemaLocation);
    xsdSchema.getContents().add(0, xsdImport);
  }

  protected void createOtherSchema(EPackage ePackage)
  {
    XSDSchema otherSchema = XSDFactory.eINSTANCE.createXSDSchema();

    otherSchema.setTargetNamespace(ePackage.getNsURI());
    otherSchema.setSchemaForSchemaQNamePrefix("xsd");

    Map<String, String> namespaces = otherSchema.getQNamePrefixToNamespaceMap();
    namespaces.put(getUniqueNsPrefix(ePackage), otherSchema.getTargetNamespace());
    namespaces.put(otherSchema.getSchemaForSchemaQNamePrefix(), XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001);

    ePackageToXSDSchemaMap.put(ePackage, otherSchema);

    // map(otherSchema, ePackage);
  }

  protected void processClasses()
  {
    for (EClassifier classifier : ePackage.getEClassifiers())
    {
      if (classifier instanceof EClass)
      {
        processClass((EClass)classifier);
      }
    }
  }

  protected void processEnums()
  {
    for (EClassifier classifier : ePackage.getEClassifiers())
    {
      if (classifier instanceof EEnum)
      {
        processEnum((EEnum)classifier);
      }
    }
  }

  /**
   * Return the given name, or the name from the XMLMap, if there is one.
   */
  protected String getName(ENamedElement element)
  {
    if (qNameMap == null)
    {
      return element.getName();
    }
    else
    {
      return qNameMap.getName(element);
    }
  }

  /**
   * Each EEnum is mapped to a schema simple type that restricts the string simple type;
   * the schema type has an enumeration facet for each EEnumLiteral.
   */
  protected void processEnum(EEnum eEnum)
  {
    XSDSimpleTypeDefinition enumType = XSDFactory.eINSTANCE.createXSDSimpleTypeDefinition();
    enumType.setName(getName(eEnum));
    enumType.setBaseTypeDefinition(xsdSchema.getSchemaForSchema().resolveSimpleTypeDefinition("NCName"));
    xsdSchema.getContents().add(enumType);

    map(enumType, eEnum);

    for (EEnumLiteral literal : eEnum.getELiterals())
    {
      XSDEnumerationFacet facet = XSDFactory.eINSTANCE.createXSDEnumerationFacet();
      facet.setLexicalValue(getName(literal));
      enumType.getFacetContents().add(facet);

      map(facet, literal);
    }
  }

  /**
   * Map each EClass to a schema complex type and an element declaration of that type;
   * the names of the complex type and element declaration are the name of the class.
   * If the class is abstract, only a complex type is created from it; there is no
   * corresponding element declaration.
   */
  protected void processClass(EClass eClass)
  {
    XSDComplexTypeDefinition xsdComplexTypeDefinition = XSDFactory.eINSTANCE.createXSDComplexTypeDefinition();
    xsdComplexTypeDefinition.setName(getName(eClass));
    if (eClass.isAbstract())
    {
      xsdComplexTypeDefinition.setAbstract(true);
    }
    List<EClass> superClasses = eClass.getESuperTypes();

    if (superClasses.size() > 0)
    {
      xsdComplexTypeDefinition.setDerivationMethod(XSDDerivationMethod.EXTENSION_LITERAL);
      EClass superClass = superClasses.get(0);

      if (superClass.getEPackage() == ePackage)
      {
        xsdComplexTypeDefinition.setBaseTypeDefinition(xsdSchema.resolveTypeDefinition(getName(superClass)));
      }
      else
      {
        xsdComplexTypeDefinition.setBaseTypeDefinition(typeInOtherSchema(superClass));
      }
    }

    xsdSchema.getContents().add(xsdComplexTypeDefinition);
    map(xsdComplexTypeDefinition, eClass);

    List<EStructuralFeature> features = getFeatures(eClass, superClasses);
    for (EStructuralFeature f : features)
    {
      if (f instanceof EAttribute)
      {
        processAttribute((EAttribute)f, xsdComplexTypeDefinition);
      }
      else if (f instanceof EReference)
      {
        processReference((EReference)f, xsdComplexTypeDefinition);
      }
    }

    if (makeClassElementDeclaration(eClass))
    {
      XSDElementDeclaration xsdElementDeclaration = XSDFactory.eINSTANCE.createXSDElementDeclaration();
      xsdElementDeclaration.setName(getName(eClass));
      xsdElementDeclaration.setTypeDefinition(xsdComplexTypeDefinition);
      xsdSchema.getContents().add(xsdElementDeclaration);

      map(xsdElementDeclaration, eClass);
    }

    additionalProcessing(eClass, xsdComplexTypeDefinition);
  }

  protected boolean makeClassElementDeclaration(EClass eClass)
  {
    return !eClass.isAbstract();
  }

  protected void additionalProcessing(EClass cls, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    // Do nothing
  }

  /**
   * Return the inherited features from the super classes (except the first one, which
   * is the base type for the generated schema type for the class), and the local
   * features.
   */
  protected List<EStructuralFeature> getFeatures(EClass eClass, List<EClass> superClasses)
  {
    List<EStructuralFeature> features = new ArrayList<EStructuralFeature>();

    if (superClasses.size() > 0)
    {
      List<EClass> allSupers = superClasses.get(0).getEAllSuperTypes();
      Set<EClass> allSuperClasses = new HashSet<EClass>();
      allSuperClasses.addAll(allSupers);
      allSuperClasses.add(superClasses.get(0));

      for (int i = 1; i < superClasses.size(); i++)
      {
        EClass superClass = superClasses.get(i);
        if (!allSuperClasses.contains(superClass))
        {
          features.addAll(getAllFeatures(superClass, allSuperClasses));
        }
      }
    }

    features.addAll(eClass.getEAttributes());
    features.addAll(eClass.getEReferences());
    return features;
  }

  /**
   * This method returns the list of features for the given class and all inherited
   * features; ignoring features from classes in the classesToIgnore set.
   */
  protected List<EStructuralFeature> getAllFeatures(EClass eClass, Set<EClass> classesToIgnore)
  {
    List<EStructuralFeature> features = new ArrayList<EStructuralFeature>();
    List<EClass> superClasses = eClass.getESuperTypes();
    
    for (int i = 0; i < superClasses.size(); i++)
    {
      if (!classesToIgnore.contains(superClasses.get(i)))
      {
        features.addAll(getAllFeatures(superClasses.get(i), classesToIgnore));
      }
    }

    features.addAll(eClass.getEAttributes());
    features.addAll(eClass.getEReferences());
    
    // the class has been processed and should be ignored if seen again in the hierarchy
    classesToIgnore.add(eClass);
    return features;
  }

  /**
   * Map each EAttribute to an XML attribute declaration in the complex type
   * corresponding to the class, or an XML element declaration, depending on
   * the multiplicity of the EAttribute.
   * <p>
   * The types are mapped as follows:
   *    Ecore type             Schema type
   *   ------------           -------------
   *     EBoolean                boolean
   *     EBooleanObject          boolean
   *     EInt                    int
   *     EIntegerObject          int
   *     ELong                   long
   *     ELongObject             long
   *     EFloat                  float
   *     EFloatObject            float
   *     EDouble                 double
   *     EDoubleObject           double
   *     EString                 string
   * <p>
   * If the attribute has no type or if the type is an EDataType that is not
   * an EEnum, the schema string type is used.
   * <p>
   * If the EAttribute is required, the attribute declaration includes
   * use="required".
   * <p>
   * If the EAttribute has a defaultValueLiteral, the attribute declaration
   * has a default attribute whose value is the defaultValueLiteral.
   * <p>
   * If the EAttribute is transient, or its type is not serializable, an
   * attribute declaration is not generated.
   * <p>
   * If the EAttribute can have many values, an element declaration is created
   * rather than an attribute declaration. The lower bound of the attribute is set
   * to the minOccurs value of the element declaration. The upper bound of the
   * attribute is set to the maxOccurs value of the element declaration (converting
   * -1 to unbounded as appropriate). The type of the element is set as above for
   * attribute declarations.
   */
  protected void processAttribute(EAttribute attribute, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    if (processAttribute(attribute))
    {
      if (makeAttributeDeclaration(attribute))
      {
        createAttributeDeclaration(attribute, xsdComplexTypeDefinition);
      }

      if (makeAttributeElementDeclaration(attribute))
      {
        createAttributeElementDeclaration(attribute, xsdComplexTypeDefinition);
      }
    }
  }

  protected void createAttributeDeclaration(EAttribute attribute, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    XSDAttributeDeclaration attrDecl = XSDFactory.eINSTANCE.createXSDAttributeDeclaration();
    attrDecl.setName(getName(attribute));
    setAttributeType(attribute, attrDecl);
    setDefaultValue(attribute, attrDecl);

    XSDAttributeUse attrUse = XSDFactory.eINSTANCE.createXSDAttributeUse();
    setUseToRequired(attribute, attrUse);
    attrUse.setContent(attrDecl);
    xsdComplexTypeDefinition.getAttributeContents().add(attrUse);

    map(attrUse, attribute);
  }

  protected void createAttributeElementDeclaration(EAttribute attribute, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    XSDModelGroup modelGroup = getModelGroup(xsdComplexTypeDefinition);
    XSDElementDeclaration xsdElementDeclaration = XSDFactory.eINSTANCE.createXSDElementDeclaration();
    xsdElementDeclaration.setName(getName(attribute));

    // If the attribute can have a null value (primitives and enums can't), and
    // if the attribute can hold many nulls or
    // it can hold only a single value that is allowed to be null 
    // and it can be considered set when it has the null value...
    //
    if (attribute.getEType().getDefaultValue() == null
      && (attribute.isMany() || (!attribute.isRequired() && (attribute.getDefaultValueLiteral() != null || attribute.isUnsettable()))))
    {
      xsdElementDeclaration.setNillable(true);
    }

    if (xsdElementDeclaration.isNillable() || attribute.isMany() || !minimizedXMI)
    {
      XSDSimpleTypeDefinition attrType = getType(attribute.getEAttributeType());

      if (attrType != null)
      {
        xsdElementDeclaration.setTypeDefinition(attrType);
      }

      XSDParticle particle = XSDFactory.eINSTANCE.createXSDParticle();
      particle.setContent(xsdElementDeclaration);
      setAttributeElementMultiplicity(attribute, particle);
      modelGroup.getContents().add(particle);
      map(particle, attribute);
    }
  }

  protected void setAttributeElementMultiplicity(EAttribute attribute, XSDParticle particle)
  {
    if (enforceLowerBound)
    {
      particle.setMinOccurs(attribute.getLowerBound());
    }
    else
    {
      particle.setMinOccurs(0);
    }

    particle.setMaxOccurs(attribute.getUpperBound());
  }

  protected boolean processAttribute(EAttribute attribute)
  {
    if (attribute.isTransient())
    {
      return false;
    }

    EDataType type = attribute.getEAttributeType();

    if (type != null && !type.isSerializable())
    {
      return false;
    }

    return true;
  }

  protected boolean makeAttributeDeclaration(EAttribute attribute)
  {
    return !attribute.isMany();
  }

  protected boolean makeAttributeElementDeclaration(EAttribute attribute)
  {
    return attribute.isMany();
  }

  /**
   * Returns the model group for the given complex type definition. If there
   * is no model group, a model group is created and added to the complex
   * type definition.
   */
  protected XSDModelGroup getModelGroup(XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    if (xsdComplexTypeDefinition.getContent() == null)
    {
      return createModelGroup(xsdComplexTypeDefinition);
    }
    else
    {
      XSDParticle particle = (XSDParticle)xsdComplexTypeDefinition.getContent();
      return (XSDModelGroup)particle.getContent();
    }
  }

  protected XSDModelGroup createModelGroup(XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    XSDModelGroup modelGroup = XSDFactory.eINSTANCE.createXSDModelGroup();
    modelGroup.setCompositor(XSDCompositor.SEQUENCE_LITERAL);
    XSDParticle particle = XSDFactory.eINSTANCE.createXSDParticle();
    particle.setContent(modelGroup);
    xsdComplexTypeDefinition.setContent(particle);
    return modelGroup;
  }

  protected void setAttributeType(EAttribute attribute, XSDAttributeDeclaration attrDecl)
  {
    XSDSimpleTypeDefinition attrType = getType(attribute.getEAttributeType());

    if (attrType != null)
    {
      attrDecl.setTypeDefinition(attrType);
    }
  }

  protected void setUseToRequired(EAttribute attribute, XSDAttributeUse attrUse)
  {
    if (attribute.isRequired())  
    {
      attrUse.setUse(XSDAttributeUseCategory.REQUIRED_LITERAL);
    }
  }

  protected void setDefaultValue(EAttribute attribute, XSDAttributeDeclaration attrDecl)
  {
    if (attribute.getDefaultValueLiteral() != null)
    {
      attrDecl.setConstraint(XSDConstraint.DEFAULT_LITERAL);
      attrDecl.setLexicalValue(attribute.getDefaultValueLiteral());
    }
  }

  protected void processReference(EReference reference, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    if (!skipReference(reference))
    {
      if (makeReferenceAttribute(reference))
      {
        makeReferenceAttribute(reference, xsdComplexTypeDefinition);
      }

      if (makeReferenceElement(reference))
      {
        makeReferenceElement(reference, xsdComplexTypeDefinition);
      }
    }
  }

  protected boolean makeReferenceAttribute(EReference reference)
  {
    return !reference.isContainment();
  }

  protected boolean makeReferenceElement(EReference reference)
  {
    return reference.isContainment() && !reference.isContainer();
  }

  protected boolean skipReference(EReference reference)
  {
    return reference.isTransient();
  }

  protected void makeReferenceAttribute(EReference reference, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    XSDAttributeDeclaration attrDecl = XSDFactory.eINSTANCE.createXSDAttributeDeclaration();
    attrDecl.setName(getName(reference));
    setReferenceAttribType(attrDecl);
    XSDAttributeUse attrUse = XSDFactory.eINSTANCE.createXSDAttributeUse();
    attrUse.setContent(attrDecl);
    xsdComplexTypeDefinition.getAttributeContents().add(attrUse);

    map(attrUse, reference);
  }

  protected void setReferenceAttribType(XSDAttributeDeclaration xsdAttributeDeclaration)
  {
    addEMFSchema();
    xsdAttributeDeclaration.setTypeDefinition(xsdSchema.getSchemaForSchema().resolveSimpleTypeDefinition(REFERENCE_TYPE_NAME));
  }

  protected void makeReferenceElement(EReference reference, XSDComplexTypeDefinition xsdComplexTypeDefinition)
  {
    XSDModelGroup modelGroup = getModelGroup(xsdComplexTypeDefinition);
    XSDElementDeclaration xsdElementDeclaration = XSDFactory.eINSTANCE.createXSDElementDeclaration();
    xsdElementDeclaration.setName(getName(reference));

    if (reference.isUnsettable() && !reference.isMany())
    {
      xsdElementDeclaration.setNillable(true);
    }

    setReferenceElementType(reference, xsdElementDeclaration);

    XSDParticle particle = XSDFactory.eINSTANCE.createXSDParticle();
    particle.setContent(xsdElementDeclaration);

    setReferenceElementMultiplicity(reference, particle);

    modelGroup.getContents().add(particle);
    map(particle, reference);
  }

  protected void setReferenceElementType(EReference reference, XSDElementDeclaration xsdElementDeclaration)
  {
    if (reference.getEType() != null)
    {
      XSDTypeDefinition type;

      if (reference.getEType().getEPackage() == ePackage)
      {
        type = xsdSchema.resolveTypeDefinition(getName(reference.getEType()));
      }
      else
      {
        type = typeInOtherSchema(reference.getEType());
      }

      if (type != null)
      {
        xsdElementDeclaration.setTypeDefinition(type);
      }
    }
  }

  protected void setReferenceElementMultiplicity(EReference reference, XSDParticle particle)
  {
    particle.setMinOccurs(reference.getLowerBound());
    particle.setMaxOccurs(reference.getUpperBound());
  }

  protected XSDSimpleTypeDefinition getType(EDataType dataType)
  {
    if (dataType instanceof EEnum)
    {
      EPackage typePkg = dataType.getEPackage();

      if (typePkg == ePackage)
      {
        return xsdSchema.resolveSimpleTypeDefinition(getName(dataType));
      }
      else
      {
        return (XSDSimpleTypeDefinition)typeInOtherSchema(dataType);
      }
    }

    String name = null;

    if (dataType != null)
    {
      name = getName(dataType);
    }

    return xsdSchema.getSchemaForSchema().resolveSimpleTypeDefinition(getSchemaName(name));
  }

  private String getSchemaName(String name)
  {
    if (ecoreToSchemaName == null)
    {
      ecoreToSchemaName = new HashMap<String, String>();
      ecoreToSchemaName.put("EBoolean", "boolean");
      ecoreToSchemaName.put("EBooleanObject", "boolean");
      ecoreToSchemaName.put("EInt", "int");
      ecoreToSchemaName.put("EIntegerObject", "int");
      ecoreToSchemaName.put("ELong", "long");
      ecoreToSchemaName.put("ELongObject", "long");
      ecoreToSchemaName.put("EFloat", "float");
      ecoreToSchemaName.put("EFloatObject", "float");
      ecoreToSchemaName.put("EDouble", "double");
      ecoreToSchemaName.put("EDoubleObject", "double");
      ecoreToSchemaName.put("EString", "string");
    }

    String schemaName = ecoreToSchemaName.get(name);

    if (schemaName != null)
    {
      return schemaName;
    }
    else
    {
      return "string";
    }
  }

  protected void addDocumentation(XSDConcreteComponent xsdComponent, EModelElement eModelElement)
  {
    if (EcoreUtil.isAncestor(ePackage, eModelElement))
    {
      String documentation = EcoreUtil.getDocumentation(eModelElement);
      if (documentation != null)
      {
        XSDAnnotation xsdAnnotation = null;
        if (xsdComponent instanceof XSDAttributeUse)
        {
          xsdComponent = ((XSDAttributeUse)xsdComponent).getContent();
        }
        else if (xsdComponent instanceof XSDParticle)
        {
          xsdComponent = ((XSDParticle)xsdComponent).getContent();
        }

        if (xsdComponent instanceof XSDSchema)
        {
          ((XSDSchema)xsdComponent).getContents().add(0, xsdAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation());
        }
        else if (xsdComponent instanceof XSDTypeDefinition)
        {
          ((XSDTypeDefinition)xsdComponent).setAnnotation(xsdAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation());
        }
        else if (xsdComponent instanceof XSDFacet)
        {
          ((XSDFacet)xsdComponent).setAnnotation(xsdAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation());
        }
        else if (xsdComponent instanceof XSDElementDeclaration)
        {
          ((XSDElementDeclaration)xsdComponent).setAnnotation(xsdAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation());
        }
        else if (xsdComponent instanceof XSDAttributeDeclaration)
        {
          ((XSDAttributeDeclaration)xsdComponent).setAnnotation(xsdAnnotation = XSDFactory.eINSTANCE.createXSDAnnotation());
        }

        if (xsdAnnotation != null)
        {
          Element userInformation = xsdAnnotation.createUserInformation(null);

          // Try to parse the documentation and use the parsed version if it's successful.
          //
          if (xsdParser == null)
          {
            xsdParser = new XSDParser(null);
          }
          xsdParser.parseString("<documentation>" + documentation + "</documentation>");
          Document document = xsdParser.getDocument();
          Document xsdDocument = userInformation.getOwnerDocument();
          if (xsdParser.getDiagnostics().isEmpty() && document.getDocumentElement().getFirstChild() != null)
          {
            for (Node node = document.getDocumentElement().getFirstChild(); node != null; node = node.getNextSibling())
            {
              userInformation.appendChild(xsdDocument.importNode(node, true));
            }
          }
          else
          {
            userInformation.appendChild(xsdDocument.createTextNode(documentation));
          }

          xsdAnnotation.getElement().appendChild(userInformation);
        }
      }
    }
  }
}
