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


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EAnnotation;
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.EModelElement;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.eclipse.emf.ecore.xml.type.util.XMLTypeUtil;


/**
 *  This is a basic implementation of the extended metadata API.
 */
public class BasicExtendedMetaData implements ExtendedMetaData
{
  protected String annotationURI;
  protected EPackage.Registry registry;
  protected EPackage.Registry demandRegistry;
  protected Map<EModelElement, Object> extendedMetaDataHolderCache;
  protected Map<EModelElement, EAnnotation> annotationMap;

  public BasicExtendedMetaData()
  {
    this(ANNOTATION_URI, EPackage.Registry.INSTANCE);
  }

  public BasicExtendedMetaData(EPackage.Registry registry)
  {
    this(ANNOTATION_URI, registry);
  }

  public BasicExtendedMetaData(String annotationURI, EPackage.Registry registry)
  {
    this(annotationURI, registry, null);
  }

  public BasicExtendedMetaData(String annotationURI, EPackage.Registry registry, Map<EModelElement, EAnnotation> annotationMap)
  {
    this.annotationURI = annotationURI.intern();
    this.registry = registry;
    this.demandRegistry = new org.eclipse.emf.ecore.impl.EPackageRegistryImpl();
    this.annotationMap = annotationMap;
    
    if (annotationURI != ANNOTATION_URI)
    {
      extendedMetaDataHolderCache = new HashMap<EModelElement, Object>();
    }
  }
  
  protected EAnnotation getAnnotation(EModelElement eModelElement, boolean demandCreate)
  {
    if (annotationMap != null)
    {
      EAnnotation result = annotationMap.get(eModelElement);
      if (result == null && demandCreate)
      {
        result = EcoreFactory.eINSTANCE.createEAnnotation();
        result.setSource(annotationURI);
        annotationMap.put(eModelElement, result);
      }
      return result;
    }
    else
    {
      EAnnotation result = eModelElement.getEAnnotation(annotationURI);
      if (result == null && demandCreate)
      {
        result = EcoreFactory.eINSTANCE.createEAnnotation();
        result.setSource(annotationURI);
        eModelElement.getEAnnotations().add(result);
      }
      return result;
    } 
  }

  public EClassifier getType(EPackage ePackage, String name)
  {
    return getExtendedMetaData(ePackage).getType(name);
  }

  public EPackage getPackage(String namespace)
  {
    EPackage ePackage = registry.getEPackage(namespace);
/*
    if (ePackage == null)
    {
      ePackage = demandRegistry.getEPackage(namespace);
    }
*/
    return ePackage;
  }

  public void putPackage(String namespace, EPackage ePackage)
  {
    registry.put(namespace, ePackage);
  }

  public EClass getDocumentRoot(EPackage ePackage)
  {
    return (EClass)getType(ePackage, "");
  }

  public void setDocumentRoot(EClass eClass)
  {
    setName(eClass, "");
    setContentKind(eClass, MIXED_CONTENT);
  }

  public boolean isDocumentRoot(EClass eClass)
  {
    return "".equals(getName(eClass));
  }

  public EReference getXMLNSPrefixMapFeature(EClass eClass)
  {
    if (getContentKind(eClass) == MIXED_CONTENT)
    {
      List<EReference> eAllReferences = eClass.getEAllReferences();
      for (int i = 0, size = eAllReferences.size();  i < size; ++i)
      {
        EReference eReference = eAllReferences.get(i);
        if ("xmlns:prefix".equals(getName(eReference)))
        {
          return eReference;
        }
      }
    }

    return null;
  }

  public EReference getXSISchemaLocationMapFeature(EClass eClass)
  {
    if (getContentKind(eClass) == MIXED_CONTENT)
    {
      List<EReference> eAllReferences = eClass.getEAllReferences();
      for (int i = 0, size = eAllReferences.size();  i < size; ++i)
      {
        EReference eReference = eAllReferences.get(i);
        if ("xsi:schemaLocation".equals(getName(eReference)))
        {
          return eReference;
        }
      }
    }

    return null;
  }

  public boolean isQualified(EPackage ePackage)
  {
    return getExtendedMetaData(ePackage).isQualified();
  }

  protected boolean basicIsQualified(EPackage ePackage)
  {
    EAnnotation eAnnotation = getAnnotation(ePackage, false);
    return eAnnotation == null || !"false".equals(eAnnotation.getDetails().get("qualified"));
  }

  public void setQualified(EPackage ePackage, boolean isQualified)
  {
    if (!isQualified)
    {
      EAnnotation eAnnotation = getAnnotation(ePackage, true);
      eAnnotation.getDetails().put("qualified", "false");
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(ePackage, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("qualified");
      }
    }
    getExtendedMetaData(ePackage).setQualified(isQualified);
  }

  public String getNamespace(EPackage ePackage)
  {
    if (isQualified(ePackage))
    {
      return (ePackage).getNsURI();
    }
    else
    {
      return null;
    }
  }

  public String getNamespace(EClassifier eClassifier)
  {
    EPackage ePackage = eClassifier.getEPackage();
    return ePackage == null ? null : getNamespace(ePackage);
  }

  public String getNamespace(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getNamespace();
  }

  /**
   * @since 2.14
   */
  protected String getPackageNamespace(EStructuralFeature eStructuralFeature)
  {
    EClass eContainingClass = eStructuralFeature.getEContainingClass();
    if (eContainingClass != null)
    {
      EPackage ePackage = eContainingClass.getEPackage();
      if (ePackage != null)
      {
        return getNamespace(ePackage);
      }
    }
    
    return null;
  }

  public String basicGetNamespace(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation == null)
    {
      return null;
    }
    else
    {
      String result = eAnnotation.getDetails().get("namespace");
      if ("##targetNamespace".equals(result))
      {
        return getPackageNamespace(eStructuralFeature);
      }
      else
      {
        return result;
      }
    }
  }

  public void setNamespace(EStructuralFeature eStructuralFeature, String namespace)
  {
    String packageNamespace = getPackageNamespace(eStructuralFeature);
    String convertedNamespace = namespace;
    if (namespace == null ? packageNamespace == null : namespace.equals(packageNamespace))
    {
      convertedNamespace="##targetNamespace";
    }

    if (convertedNamespace != null)
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put("namespace", convertedNamespace);
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("namespace");
      }
    }
    getExtendedMetaData(eStructuralFeature).setNamespace(namespace);
  }

  public String getName(EClassifier eClassifier)
  {
    return getExtendedMetaData(eClassifier).getName();
  }

  protected String basicGetName(EClassifier eClassifier)
  {
    EAnnotation eAnnotation = getAnnotation(eClassifier, false);
    if (eAnnotation != null)
    {
      String result = eAnnotation.getDetails().get("name");
      if (result != null)
      {
        return result;
      }
    }
    return eClassifier.getName();
  }

  public void setName(EClassifier eClassifier, String name)
  {
    EAnnotation eAnnotation = getAnnotation(eClassifier, true);
    eAnnotation.getDetails().put("name", name);
    getExtendedMetaData(eClassifier).setName(name);
    EPackage ePackage = eClassifier.getEPackage();
    if (ePackage != null)
    {
      getExtendedMetaData(ePackage).rename(eClassifier, name);
    }
  }

  public boolean isAnonymous(EClassifier eClassifier)
  {
    String name =  getExtendedMetaData(eClassifier).getName();
    return name.length() == 0 || name.contains("_._");
  }

  public String getName(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getName();
  }

  protected String basicGetName(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      String result = eAnnotation.getDetails().get("name");
      if (result != null)
      {
        return result;
      }
    }
    return eStructuralFeature.getName();
  }

  public void setName(EStructuralFeature eStructuralFeature, String name)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
    eAnnotation.getDetails().put("name", name);
    getExtendedMetaData(eStructuralFeature).setName(name);
  }

  protected String getQualifiedName(String defaultNamespace, EClassifier eClassifier)
  {
    String namespace = getNamespace(eClassifier);
    String name = getName(eClassifier);
    if (namespace == null)
    {
      return namespace == defaultNamespace ? name : "#" + name;
    }
    else
    {
      return namespace.equals(defaultNamespace) ? name : namespace + "#" + name;
    }
  }

  protected String getQualifiedName(String defaultNamespace, EStructuralFeature eStructuralFeature)
  {
    String namespace = getNamespace(eStructuralFeature);
    String name = getName(eStructuralFeature);
    if (namespace == null)
    {
      return namespace == defaultNamespace ? name : "#" + name;
    }
    else
    {
      return namespace.equals(defaultNamespace) ? name : namespace + "#" + name;
    }
  }

  public EClassifier getType(String namespace, String name)
  {
    EPackage ePackage = getPackage(namespace);
    return ePackage == null ? null : getType(ePackage, name);
  }

  public EStructuralFeature getAttribute(String namespace, String name)
  {
    EPackage ePackage = getPackage(namespace);
    if (ePackage != null)
    {
      EClass documentRoot = getDocumentRoot(ePackage);
      if (documentRoot != null)
      {
        return getLocalAttribute(documentRoot, namespace, name);
      }
    }

    return null;
  }

  public EStructuralFeature getElement(String namespace, String name)
  {
    EPackage ePackage = getPackage(namespace);
    if (ePackage != null)
    {
      EClass documentRoot = getDocumentRoot(ePackage);
      if (documentRoot != null)
      {
        return getLocalElement(documentRoot, namespace, name);
      }
    }

    return null;
  }

  public int getFeatureKind(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getFeatureKind();
  }

  protected int basicGetFeatureKind(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      Object kind = eAnnotation.getDetails().get("kind");
      if (kind != null)
      {
        for (int i = 1; i < FEATURE_KINDS.length; ++i)
        {
          if (FEATURE_KINDS[i].equals(kind))
          {
            return i;
          }
        }
      }
    }

    return 0;
  }

  public void setFeatureKind(EStructuralFeature eStructuralFeature, int kind)
  {
    if (kind > 0 && kind < FEATURE_KINDS.length)
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put("kind", FEATURE_KINDS[kind]);
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("kind");
      }
    }
    getExtendedMetaData(eStructuralFeature).setFeatureKind(kind);
  }

  public int getContentKind(EClass eClass)
  {
    return getExtendedMetaData(eClass).getContentKind();
  }

  protected int basicGetContentKind(EClass eClass)
  {
    EAnnotation eAnnotation = getAnnotation(eClass, false);
    if (eAnnotation != null)
    {
      Object kind = eAnnotation.getDetails().get("kind");
      if (kind != null)
      {
        for (int i = 1; i < CONTENT_KINDS.length; ++i)
        {
          if (CONTENT_KINDS[i].equals(kind))
          {
            return i;
          }
        }
      }
    }

    return 0;
  }

  public void setContentKind(EClass eClass, int kind)
  {
    if (kind > 0 && kind < CONTENT_KINDS.length)
    {
      EAnnotation eAnnotation = getAnnotation(eClass, true);
      eAnnotation.getDetails().put("kind", CONTENT_KINDS[kind]);
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eClass, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("kind");
      }
    }
    getExtendedMetaData(eClass).setContentKind(kind);
  }

  public int getDerivationKind(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getDerivationKind();
  }

  protected int basicGetDerivationKind(EClassifier eClassifier)
  {
    EAnnotation eAnnotation = getAnnotation(eClassifier, false);
    if (eAnnotation != null)
    {
      EMap<String, String> details = eAnnotation.getDetails();
      Object kind = details.get("restriction");
      if (kind != null)
      {
        return RESTRICTION_DERIVATION;
      }
      kind = details.get("list");
      if (kind != null)
      {
        return LIST_DERIVATION;
      }
      kind = details.get("union");
      if (kind != null)
      {
        return UNION_DERIVATION;
      }
    }

    return 0;
  }

  public EDataType getBaseType(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getBaseType();
  }

  public EDataType basicGetBaseType(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      EMap<String, String> details = eAnnotation.getDetails();
      String baseType = details.get("baseType");
      EClassifier type = getEClassifier(eDataType.getEPackage(), baseType);
      if (type instanceof EDataType)
      {
        return (EDataType)type;
      }
    }

    return null;
  }

  /**
   * @since 2.14
   */
  protected EClassifier getEClassifier(EPackage ePackage, String type)
  {
    if (type == null)
    {
      return null;
    }
    else
    {
      int index = type.lastIndexOf("#");
      EClassifier eClassifier = 
        index == -1 ?
          ePackage == null ? null : getType(ePackage, type) :
          index == 0 ?
            getType((String)null, type.substring(1)) :
            getType(type.substring(0, index), type.substring(index + 1));
      return eClassifier;
    }
  }

  public void setBaseType(EDataType eDataType, EDataType baseType)
  {
    if (baseType == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("baseType");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("baseType", getQualifiedName(getNamespace(eDataType), baseType));
    }
    getExtendedMetaData(eDataType).setBaseType(baseType);
  }

  public EDataType getItemType(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getItemType();
  }

  protected EDataType basicGetItemType(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      EMap<String, String> details = eAnnotation.getDetails();
      String itemType = details.get("itemType");
      EClassifier type = getEClassifier(eDataType.getEPackage(), itemType);
      if (type instanceof EDataType)
      {
        return (EDataType)type;
      }
    }

    return null;
  }

  public void setItemType(EDataType eDataType, EDataType itemType)
  {
    if (itemType == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("itemType");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("itemType", getQualifiedName(getNamespace(eDataType), itemType));
    }
    getExtendedMetaData(eDataType).setItemType(itemType);
  }

  public List<EDataType> getMemberTypes(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMemberTypes();
  }

  protected List<EDataType> basicGetMemberTypes(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String memberTypes = eAnnotation.getDetails().get("memberTypes");
      if (memberTypes != null)
      {
        List<EDataType> result = new ArrayList<EDataType>();
        EPackage ePackage = eDataType.getEPackage();
        for (StringTokenizer stringTokenizer = new StringTokenizer(memberTypes); stringTokenizer.hasMoreTokens(); )
        {
          String member = stringTokenizer.nextToken();
          EClassifier type = getEClassifier(ePackage, member);
          if (type instanceof EDataType)
          {
            result.add((EDataType)type);
          }
        }
        return result;
      }
    }

    return Collections.emptyList();
  }

  public void setMemberTypes(EDataType eDataType, List<EDataType> memberTypes)
  {
    if (memberTypes.isEmpty())
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("memberTypes");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      String namespace = getNamespace(eDataType);
      StringBuilder result = new StringBuilder();
      for (int i = 0, size = memberTypes.size(); i < size; ++i)
      {
        result.append(getQualifiedName(namespace, memberTypes.get(i)));
        result.append(' ');
      }
      eAnnotation.getDetails().put("memberTypes", result.substring(0, result.length() - 1));
    }
    getExtendedMetaData(eDataType).setMemberTypes(memberTypes);
  }

  protected boolean isFeatureKindSpecific()
  {
    return true;
  }

  protected boolean isFeatureNamespaceMatchingLax()
  {
    return false;
  }

  public EStructuralFeature getLocalAttribute(EClass eClass, String namespace, String name)
  {
    EStructuralFeature result = null;
    if (isFeatureKindSpecific())
    {
      List<EStructuralFeature> allAttributes = getAllAttributes(eClass);
      for (int i = 0, size = allAttributes.size(); i < size; ++i)
      {
        EStructuralFeature eStructuralFeature = allAttributes.get(i);
        if (name.equals(getName(eStructuralFeature)))
        {
          String featureNamespace = getNamespace(eStructuralFeature);
          if (namespace == null) 
          {
            if (featureNamespace == null)
            {
              return eStructuralFeature;
            }
            else if (result == null)
            {
              result = eStructuralFeature;
            }
          }
          else if (namespace.equals(featureNamespace))
          {
            return eStructuralFeature;
          }
          else if (featureNamespace == null && result == null)
          {
            result = eStructuralFeature;
          }
        }
      }
    }
    else
    {
      for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
      {
        EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i);
        switch (getFeatureKind(eStructuralFeature))
        {
          case UNSPECIFIED_FEATURE:
          case ATTRIBUTE_FEATURE:
          {
            if (name.equals(getName(eStructuralFeature)))
            {
              String featureNamespace = getNamespace(eStructuralFeature);
              if (namespace == null) 
              {
                if (featureNamespace == null)
                {
                  return eStructuralFeature;
                }
                else if (result == null)
                {
                  result = eStructuralFeature;
                }
              }
              else if (namespace.equals(featureNamespace))
              {
                return eStructuralFeature;
              }
              else if (featureNamespace == null && result == null)
              {
                result = eStructuralFeature;
              }
            }
            break;
          }
        }
      }
    }

    return isFeatureNamespaceMatchingLax() ? result : null;
  }

  public EStructuralFeature getAttribute(EClass eClass, String namespace, String name)
  {
    EStructuralFeature result = getLocalAttribute(eClass, namespace, name);
    if (result == null)
    {
      result = getAttribute(namespace, name);
      if (result != null && getAffiliation(eClass, result) == null)
      {
        return null;
      }
    }
    return result;
  }

  protected EStructuralFeature getLocalElement(EClass eClass, String namespace, String name)
  {
    EStructuralFeature result = null;
    if (isFeatureKindSpecific())
    {
      List<EStructuralFeature> allElements = getAllElements(eClass);
      for (int i = 0, size = allElements.size(); i < size; ++i)
      {
        EStructuralFeature eStructuralFeature = allElements.get(i);
        if (name.equals(getName(eStructuralFeature)))
        {
          String featureNamespace = getNamespace(eStructuralFeature);
          if (namespace == null) 
          {
            if (featureNamespace == null)
            {
              return eStructuralFeature;
            }
            else if (result == null)
            {
              result = eStructuralFeature;
            }
          }
          else if (namespace.equals(featureNamespace))
          {
            return eStructuralFeature;
          }
          else if (featureNamespace == null && result == null)
          {
            result = eStructuralFeature;
          }
        }
      }
    }
    else
    {
      for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
      {
        EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i);
        switch (getFeatureKind(eStructuralFeature))
        {
          case UNSPECIFIED_FEATURE:
          case GROUP_FEATURE:
          case ELEMENT_FEATURE:
          {
            if (name.equals(getName(eStructuralFeature)))
            {
              String featureNamespace = getNamespace(eStructuralFeature);
              if (namespace == null) 
              {
                if (featureNamespace == null)
                {
                  return eStructuralFeature;
                }
                else if (result == null)
                {
                  result = eStructuralFeature;
                }
              }
              else if (namespace.equals(featureNamespace))
              {
                return eStructuralFeature;
              }
              else if (featureNamespace == null && result == null)
              {
                result = eStructuralFeature;
              }
            }
            break;
          }
        }
      }
    }

    return isFeatureNamespaceMatchingLax() ? result : null;
  }

  public EStructuralFeature getElement(EClass eClass, String namespace, String name)
  {
    EStructuralFeature result = getLocalElement(eClass, namespace, name);
    if (result == null)
    {
      result = getElement(namespace, name);
      if (result != null && getAffiliation(eClass, result) == null)
      {
        return null;
      }
    }
    return result;
  }
  
  public List<EStructuralFeature> getAllAttributes(EClass eClass)
  {
    List<EClass> superTypes = eClass.getESuperTypes();
    List<EStructuralFeature> result = null;
    boolean changeable = false;
    for (int i = 0, size = superTypes.size(); i < size; ++i) 
    {
      EClass eSuperType = superTypes.get(i);
      List<EStructuralFeature> allAttributes =  getAllAttributes(eSuperType);
      if (!allAttributes.isEmpty())
      {
        if (result == null)
        {
          result = allAttributes;
        }
        else
        {
          if (!changeable)
          {
            changeable = true;
            result = new UniqueEList<EStructuralFeature>(result);
          }
          result.addAll(allAttributes);
        }
      }
    }
    List<EStructuralFeature> attributes = getAttributes(eClass);
    if (!attributes.isEmpty())
    {
      if (result == null)
      {
        return attributes;
      }
      else
      {
        if (!changeable)
        {
          result = new UniqueEList<EStructuralFeature>(result);
        }
        result.addAll(attributes);
        return result;
      }
    }
    else
    {
      return result == null ? Collections.<EStructuralFeature>emptyList() : result;
    }
  }
  
  public List<EStructuralFeature> getAllElements(EClass eClass)
  {
    List<EClass> superTypes = eClass.getESuperTypes();
    List<EStructuralFeature> result = null;
    boolean changeable = false;
    for (int i = 0, size = superTypes.size(); i < size; ++i) 
    {
      EClass eSuperType = superTypes.get(i);
      List<EStructuralFeature> allElements =  getAllElements(eSuperType);
      if (!allElements.isEmpty())
      {
        if (result == null)
        {
          result = allElements;
        }
        else
        {
          if (!changeable)
          {
            changeable = true;
            result = new UniqueEList<EStructuralFeature>(result);
          }
          result.addAll(allElements);
        }
      }
    }
    List<EStructuralFeature> elements = getElements(eClass);
    if (!elements.isEmpty())
    {
      if (result == null)
      {
        return elements;
      }
      else
      {
        if (!changeable)
        {
          result = new UniqueEList<EStructuralFeature>(result);
        }
        result.addAll(elements);
        return result;
      }
    }
    else
    {
      return result == null ? Collections.<EStructuralFeature>emptyList() : result;
    }
  }

  public List<EStructuralFeature> getAttributes(EClass eClass)
  {
    List<EStructuralFeature> eStructuralFeatures = eClass.getEStructuralFeatures();
    List<EStructuralFeature> result = null;
    for (int i = 0, size = eStructuralFeatures.size(); i < size; ++i)
    {
      EStructuralFeature eStructuralFeature = eStructuralFeatures.get(i);
      switch (getFeatureKind(eStructuralFeature))
      {
        case ATTRIBUTE_FEATURE:
        case ATTRIBUTE_WILDCARD_FEATURE:
        {
          if (result == null)
          {
            result = new ArrayList<EStructuralFeature>();
          }
          result.add(eStructuralFeature);
        }
      }
    }
    return result == null ? Collections.<EStructuralFeature>emptyList() : result;
  }

  public List<EStructuralFeature> getElements(EClass eClass)
  {
    List<EStructuralFeature> eStructuralFeatures = eClass.getEStructuralFeatures();
    List<EStructuralFeature> result = null;
    for (int i = 0, size = eStructuralFeatures.size(); i < size; ++i)
    {
      EStructuralFeature eStructuralFeature = eStructuralFeatures.get(i);
      switch (getFeatureKind(eStructuralFeature))
      {
        case ELEMENT_FEATURE:
        case ELEMENT_WILDCARD_FEATURE:
        case GROUP_FEATURE:
        {
          if (result == null)
          {
            result = new ArrayList<EStructuralFeature>();
          }
          result.add(eStructuralFeature);
          break;
        }
      }
    }
    
   return result == null ? Collections.<EStructuralFeature>emptyList() : result;
  }

  public EStructuralFeature getSimpleFeature(EClass eClass)
  {
    if (getContentKind(eClass) == SIMPLE_CONTENT)
    {
      for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
      {
        EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i);
        if (getFeatureKind(eStructuralFeature) == ExtendedMetaData.SIMPLE_FEATURE)
        {
          return eStructuralFeature;
        }
      }
    }

    return null;
  }

  public EAttribute getMixedFeature(EClass eClass)
  {
    switch (getContentKind(eClass))
    {
      case MIXED_CONTENT:
      case SIMPLE_CONTENT:
      {
        List<EAttribute> eAllAttributes = eClass.getEAllAttributes();
        for (int i = 0, size = eAllAttributes.size(); i < size; ++i)
        {
          EAttribute eAttribute = eAllAttributes.get(i);
          if (getFeatureKind(eAttribute) == ExtendedMetaData.ELEMENT_WILDCARD_FEATURE)
          {
            return eAttribute;
          }
        }
        break;
      }
    }

    return null;
  }

  public List<String> getWildcards(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getWildcards();
  }

  protected List<String> basicGetWildcards(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      String wildcards = eAnnotation.getDetails().get("wildcards");
      if (wildcards != null)
      {
        List<String> result = new ArrayList<String>();
        for (StringTokenizer stringTokenizer = new StringTokenizer(wildcards); stringTokenizer.hasMoreTokens(); )
        {
          String wildcard = stringTokenizer.nextToken();
          if (wildcard.equals("##other"))
          {
            result.add("!##" + getPackageNamespace(eStructuralFeature));
          }
          else if (wildcard.equals("##local"))
          {
            result.add(null);
          }
          else if (wildcard.equals("##targetNamespace"))
          {
            result.add(getPackageNamespace(eStructuralFeature));
          }
          else
          {
            result.add(wildcard);
          }
        }
        return result;
      }
    }

    return Collections.emptyList();
  }

  public void setWildcards(EStructuralFeature eStructuralFeature, List<String> wildcards)
  {
    if (wildcards.isEmpty())
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("wildcards");
        eAnnotation.getDetails().removeKey("name");
      }
    }
    else
    {
      String namespace = getPackageNamespace(eStructuralFeature);
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put("wildcards", getEncodedWildcards(namespace, wildcards));
      eAnnotation.getDetails().put("name","");
    }
    getExtendedMetaData(eStructuralFeature).setWildcards(wildcards);
  }
  
  public static String getEncodedWildcards(String namespace, List<String> wildcards)
  {
    if (wildcards.isEmpty())
    {
      return ""; 
    }
    else
    {
      StringBuilder value = new StringBuilder();
      for (int i = 0, size = wildcards.size(); i < size; )
      {
        String wildcard = wildcards.get(i);
        if (wildcard == null)
        {
          if (namespace == null)
          {
            value.append("##targetNamespace");
          }
          else 
          {
            value.append("##local");
          }
        }
        else if (wildcard.startsWith("!##"))
        {
          if (namespace == null ? 
                wildcard.length() == 3 : 
                wildcard.endsWith(namespace) && wildcard.length() == namespace.length() + 3)
          {
            value.append("##other");
          }
          else
          {
            value.append(wildcard);
          }
        }
        else if (wildcard.equals(namespace))
        {
          value.append("##targetNamespace");
        }
        else
        {
          value.append(wildcard);
        }

        if (++i < size)
        {
          value.append(' ');
        }
      }
      return value.toString();
    }
  }

  public int getProcessingKind(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getProcessingKind();
  }

  protected int basicGetProcessingKind(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      Object kind = eAnnotation.getDetails().get("processing");
      if (kind != null)
      {
        for (int i = 1; i < PROCESSING_KINDS.length; ++i)
        {
          if (PROCESSING_KINDS[i].equals(kind))
          {
            return i;
          }
        }
      }
    }

    return 0;
  }

  public void setProcessingKind(EStructuralFeature eStructuralFeature, int kind)
  {
    if (kind > 0 && kind < PROCESSING_KINDS.length)
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put("processing", PROCESSING_KINDS[kind]);
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("processing");
      }
    }
    getExtendedMetaData(eStructuralFeature).setProcessingKind(kind);
  }

  public EStructuralFeature getGroup(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getGroup();
  }

  protected EStructuralFeature basicGetGroup(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      String qualifiedName = eAnnotation.getDetails().get("group");
      if (qualifiedName != null)
      {
        EClass eContainingClass = eStructuralFeature.getEContainingClass();
        if (eContainingClass == null)
        {
          return null;
        }

        int fragmentIndex = qualifiedName.lastIndexOf('#');
        String namespace;
        String name;
        if (fragmentIndex == -1)
        {
          EPackage ePackage = eContainingClass.getEPackage();
          namespace = ePackage == null ? null : getNamespace(ePackage);
          name = qualifiedName;
        }
        else if (fragmentIndex == 0)
        {
          namespace = null;
          name = qualifiedName.substring(1);
        }
        else
        {
          namespace = qualifiedName.substring(0, fragmentIndex);
          name = qualifiedName.substring(fragmentIndex + 1);
        }

        switch (getFeatureKind(eStructuralFeature)) 
        {
          case ATTRIBUTE_FEATURE:
          case ATTRIBUTE_WILDCARD_FEATURE:
          {
            return 
              getAttribute
                (eContainingClass, 
                 namespace,
                 name);
          }
          case UNSPECIFIED_FEATURE:
          case ELEMENT_FEATURE:
          case ELEMENT_WILDCARD_FEATURE:
          case GROUP_FEATURE:
          {
            return 
              getElement
                (eContainingClass, 
                 namespace,
                 name);
          }
        }
      }
    }
    return null;
  }

  public void setGroup(EStructuralFeature eStructuralFeature, EStructuralFeature group)
  {
    if (group == null)
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("group");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put
        ("group", getQualifiedName(getPackageNamespace(eStructuralFeature), group));
    }
    getExtendedMetaData(eStructuralFeature).setGroup(group);
  }

  public EStructuralFeature getAffiliation(EStructuralFeature eStructuralFeature)
  {
    return getExtendedMetaData(eStructuralFeature).getAffiliation();
  }

  protected EStructuralFeature basicGetAffiliation(EStructuralFeature eStructuralFeature)
  {
    EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
    if (eAnnotation != null)
    {
      String qualifiedName = eAnnotation.getDetails().get("affiliation");
      if (qualifiedName != null)
      {
        int fragmentIndex = qualifiedName.lastIndexOf('#');
        if (fragmentIndex == -1)
        {
          EClass eContainingClass = eStructuralFeature.getEContainingClass();
          if (eContainingClass != null)
          {
            EPackage ePackage = eContainingClass.getEPackage();
            if (ePackage != null)
            {
              EClass documentRoot = getDocumentRoot(ePackage);
              if (documentRoot != null)
              {
                return getLocalElement(documentRoot, getNamespace(ePackage), qualifiedName);
              }
            }
          }
        }
        else if (fragmentIndex == 0)
        {
          return getElement(null, qualifiedName.substring(1));
        }
        else
        {
          return getElement(qualifiedName.substring(0, fragmentIndex), qualifiedName.substring(fragmentIndex + 1));
        }
      }
    }
    return null;
  }

  public void setAffiliation(EStructuralFeature eStructuralFeature, EStructuralFeature affiliation)
  {
    if (affiliation == null)
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("affiliation");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eStructuralFeature, true);
      eAnnotation.getDetails().put
        ("affiliation", getQualifiedName(getPackageNamespace(eStructuralFeature), affiliation));
    }
    getExtendedMetaData(eStructuralFeature).setAffiliation(affiliation);
  }

  public EStructuralFeature getAffiliation(EClass eClass, EStructuralFeature eStructuralFeature)
  {
    if (eClass.getFeatureID(eStructuralFeature) >= 0) 
    {
      return eStructuralFeature;
    }

    switch (getFeatureKind(eStructuralFeature))
    {
      case ATTRIBUTE_FEATURE:
      {
        EClass eContainingClass = eStructuralFeature.getEContainingClass();
        if (eContainingClass != null && isDocumentRoot(eContainingClass))
        {
          String namespace = getNamespace(eStructuralFeature);
          String name = getName(eStructuralFeature);
          EStructuralFeature result = getLocalAttribute(eClass, namespace, name);
          if (result != null)
          {
            return result;
          }
  
          List<EStructuralFeature> allAttributes = getAllAttributes(eClass);
          for (int i = 0, size = allAttributes.size(); i < size; ++i)
          {
            result = allAttributes.get(i);
            if (matches(getWildcards(result), namespace))
            {
              return result;
            }
          }
        }
        return null;
      }
      case ELEMENT_FEATURE:
      {
        EClass eContainingClass = eStructuralFeature.getEContainingClass();
        if (eContainingClass != null && isDocumentRoot(eContainingClass))
        {
          for (EStructuralFeature affiliation = eStructuralFeature; affiliation != null; affiliation = getAffiliation(affiliation))
          {
            String namespace = getNamespace(affiliation);
            String name = getName(affiliation);
            EStructuralFeature result = getLocalElement(eClass, namespace, name);
            if (result != null)
            {
              return result;
            }
          }
  
          String namespace = getNamespace(eStructuralFeature);
          if (XMLTypePackage.eNS_URI.equals(namespace))
          {
            return getMixedFeature(eClass);
          }
          else
          {
            List<EStructuralFeature> allElements = getAllElements(eClass);
            for (int i = 0, size = allElements.size(); i < size; ++i)
            {
              EStructuralFeature result = allElements.get(i);
              if (matches(getWildcards(result), namespace))
              {
                return result;
              }
            }
          }
        }
        return null;
      }
      default:
      {
        return null;
      }
    }
  }

  public EStructuralFeature getAttributeWildcardAffiliation(EClass eClass, String namespace, String name)
  {
    List<EStructuralFeature> allAttributes = getAllAttributes(eClass);
    for (int i = 0, size = allAttributes.size(); i < size; ++i)
    {
      EStructuralFeature result = allAttributes.get(i);
      if (matches(getWildcards(result), namespace))
      {
        return result;
      }
    }

    return null;
  }

  public EStructuralFeature getElementWildcardAffiliation(EClass eClass, String namespace, String name)
  {
    List<EStructuralFeature> allElements = getAllElements(eClass);
    for (int i = 0, size = allElements.size(); i < size; ++i)
    {
      EStructuralFeature result = allElements.get(i);
      if (matches(getWildcards(result), namespace))
      {
        return result;
      }
    }

    return null;
  }

  public boolean matches(List<String> wildcards, String namespace)
  {
    if (!wildcards.isEmpty())
    {
      for (int i = 0, size = wildcards.size(); i < size; ++i)
      {
        String wildcard = wildcards.get(i);
        if (matches(wildcard, namespace))
        {
          return true;
        }
      }
    }

    return false;
  }

  public boolean matches(String wildcard, String namespace)
  {
    return
      wildcard == null ?
        namespace == null :
        wildcard.startsWith("!##") ?
           namespace != null &&
             (!wildcard.endsWith(namespace) || wildcard.length() != namespace.length() + 3) && 
             !XMLTypePackage.eNS_URI.equals(namespace) :
           wildcard.equals("##any") && !XMLTypePackage.eNS_URI.equals(namespace) || wildcard.equals(namespace);
  }

  public int getWhiteSpaceFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getWhiteSpaceFacet();
  }

  protected int basicGetWhiteSpaceFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String whiteSpaceLiteral = eAnnotation.getDetails().get("whiteSpace");
      for (int i = 1; i < WHITE_SPACE_KINDS.length; ++i)
      {
        if (WHITE_SPACE_KINDS[i].equals(whiteSpaceLiteral))
        {
          return i;
        }
      }
    }
    return UNSPECIFIED_WHITE_SPACE;
  }

  public void setWhiteSpaceFacet(EDataType eDataType, int whiteSpace)
  {
    if (whiteSpace == UNSPECIFIED_WHITE_SPACE)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("whiteSpace");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("whiteSpace", WHITE_SPACE_KINDS[whiteSpace]);
    }
    getExtendedMetaData(eDataType).setWhiteSpaceFacet(whiteSpace);
  }

  public List<String> getEnumerationFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getEnumerationFacet();
  }

  protected List<String> basicGetEnumerationFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String enumerationLiteral = eAnnotation.getDetails().get("enumeration");
      if (enumerationLiteral != null)
      {
        List<String> result = new ArrayList<String>();
        for (StringTokenizer stringTokenizer = new StringTokenizer(enumerationLiteral, " "); stringTokenizer.hasMoreTokens(); )
        {
          String enumeration = replace(replace(stringTokenizer.nextToken(), "%20", " "), "%25", "%");
          result.add(enumeration);
        }
        return result;
      }
    }
    return Collections.emptyList();
  }

  public void setEnumerationFacet(EDataType eDataType, List<String> literals)
  {
    if (literals.isEmpty())
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("enumeration");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      StringBuilder result = new StringBuilder();
      for (int i = 0, size = literals.size(); i < size; ++i)
      {
        result.append(replace(replace(literals.get(i), "%","%25"), " ", "%20"));
        result.append(' ');
      }
      eAnnotation.getDetails().put("enumeration", result.substring(0, result.length() - 1));
    }
    getExtendedMetaData(eDataType).setEnumerationFacet(literals);
  }

  public List<String> getPatternFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getPatternFacet();
  }

  protected List<String> basicGetPatternFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String patternLiteral = eAnnotation.getDetails().get("pattern");
      if (patternLiteral != null)
      {
        List<String> result = new ArrayList<String>();
        for (StringTokenizer stringTokenizer = new StringTokenizer(patternLiteral, " "); stringTokenizer.hasMoreTokens(); )
        {
          String pattern = replace(replace(stringTokenizer.nextToken(), "%20", " "), "%25", "%");
          result.add(pattern);
        }
        return result;
      }
    }
    return Collections.emptyList();
  }

  public void setPatternFacet(EDataType eDataType, List<String> pattern)
  {
    if (pattern.isEmpty())
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("pattern");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      StringBuilder result = new StringBuilder();
      for (int i = 0, size = pattern.size(); i < size; ++i)
      {
        result.append(replace(replace(pattern.get(i), "%","%25"), " ", "%20"));
        result.append(' ');
      }
      eAnnotation.getDetails().put("pattern", result.substring(0, result.length() - 1));
    }
    getExtendedMetaData(eDataType).setPatternFacet(pattern);
  }

  public int getTotalDigitsFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getTotalDigitsFacet();
  }

  private int parseInt(String literal)
  {
    try
    {
      return Integer.parseInt(literal);
    }
    catch (NumberFormatException exception)
    {
      return -1;
    }
  }

  protected int basicGetTotalDigitsFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String totalDigitsLiteral = eAnnotation.getDetails().get("totalDigits");
      if (totalDigitsLiteral != null)
      {
        return parseInt(totalDigitsLiteral);
      }
    }
    return -1;
  }

  public void setTotalDigitsFacet(EDataType eDataType, int digits)
  {
    if (digits == -1)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("totalDigits");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("totalDigits", Integer.toString(digits));
    } 
    getExtendedMetaData(eDataType).setTotalDigitsFacet(digits);
  }

  public int getFractionDigitsFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getFractionDigitsFacet();
  }

  protected int basicGetFractionDigitsFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String fractionDigitsLiteral = eAnnotation.getDetails().get("fractionDigits");
      if (fractionDigitsLiteral != null)
      {
        return parseInt(fractionDigitsLiteral);
      }
    }
    return -1;
  }

  public void setFractionDigitsFacet(EDataType eDataType, int digits)
  {
    if (digits == -1)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("fractionDigits");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("fractionDigits", Integer.toString(digits));
    } 
    getExtendedMetaData(eDataType).setFractionDigitsFacet(digits);
  }

  public int getLengthFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getLengthFacet();
  }

  protected int basicGetLengthFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String lengthLiteral = eAnnotation.getDetails().get("length");
      if (lengthLiteral != null)
      {
        return parseInt(lengthLiteral);
      }
    }
    return -1;
  }

  public void setLengthFacet(EDataType eDataType, int length)
  {
    if (length == -1)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("length");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("length", Integer.toString(length));
    } 
    getExtendedMetaData(eDataType).setLengthFacet(length);
  }

  public int getMinLengthFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMinLengthFacet();
  }

  protected int basicGetMinLengthFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String minLengthLiteral = eAnnotation.getDetails().get("minLength");
      if (minLengthLiteral != null)
      {
        return parseInt(minLengthLiteral);
      }
    }
    return -1;
  }

  public void setMinLengthFacet(EDataType eDataType, int length)
  {
    if (length == -1)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("minLength");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("minLength", Integer.toString(length));
    } 
    getExtendedMetaData(eDataType).setMinLengthFacet(length);
  }

  public int getMaxLengthFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMaxLengthFacet();
  }

  protected int basicGetMaxLengthFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    if (eAnnotation != null)
    {
      String maxLengthLiteral = eAnnotation.getDetails().get("maxLength");
      if (maxLengthLiteral != null)
      {
        return parseInt(maxLengthLiteral);
      }
    }
    return -1;
  }

  public void setMaxLengthFacet(EDataType eDataType, int length)
  {
    if (length == -1)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("maxLength");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("maxLength", Integer.toString(length));
    } 
    getExtendedMetaData(eDataType).setMaxLengthFacet(length);
  }

  public String getMinExclusiveFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMinExclusiveFacet();
  }

  protected String basicGetMinExclusiveFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    return
      eAnnotation == null ? 
        null : 
        (String)eAnnotation.getDetails().get("minExclusive");
  }

  public void setMinExclusiveFacet(EDataType eDataType, String literal)
  {
    if (literal == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("minExclusive");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("minExclusive", literal);
    } 
    getExtendedMetaData(eDataType).setMinExclusiveFacet(literal);
  }

  public String getMaxExclusiveFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMaxExclusiveFacet();
  }

  protected String basicGetMaxExclusiveFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    return
      eAnnotation == null ? 
        null : 
        (String)eAnnotation.getDetails().get("maxExclusive");
  }

  public void setMaxExclusiveFacet(EDataType eDataType, String literal)
  {
    if (literal == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("maxExclusive");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("maxExclusive", literal);
    } 
    getExtendedMetaData(eDataType).setMaxExclusiveFacet(literal);
  }

  public String getMinInclusiveFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMinInclusiveFacet();
  }

  protected String basicGetMinInclusiveFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    return
      eAnnotation == null ? 
        null : 
        (String)eAnnotation.getDetails().get("minInclusive");
  }

  public void setMinInclusiveFacet(EDataType eDataType, String literal)
  {
    if (literal == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("minInclusive");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("minInclusive", literal);
    } 
    getExtendedMetaData(eDataType).setMinInclusiveFacet(literal);
  }

  public String getMaxInclusiveFacet(EDataType eDataType)
  {
    return getExtendedMetaData(eDataType).getMaxInclusiveFacet();
  }

  protected String basicGetMaxInclusiveFacet(EDataType eDataType)
  {
    EAnnotation eAnnotation = getAnnotation(eDataType, false);
    return
      eAnnotation == null ? 
        null : 
        (String)eAnnotation.getDetails().get("maxInclusive");
  }

  public void setMaxInclusiveFacet(EDataType eDataType, String literal)
  {
    if (literal == null)
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, false);
      if (eAnnotation != null)
      {
        eAnnotation.getDetails().removeKey("maxInclusive");
      }
    }
    else
    {
      EAnnotation eAnnotation = getAnnotation(eDataType, true);
      eAnnotation.getDetails().put("maxInclusive", literal);
    } 
    getExtendedMetaData(eDataType).setMaxInclusiveFacet(literal);
  }

  public EPackage demandPackage(String namespace)
  {
    EPackage ePackage = demandRegistry.getEPackage(namespace);
    if (ePackage == null)
    {
      ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setNsURI(namespace);
      setQualified(ePackage, namespace != null);
      if (namespace != null)
      {
        ePackage.setNsPrefix
          (namespace.equals(ExtendedMetaData.XMLNS_URI) ? 
             namespace.equals(ExtendedMetaData.XML_URI) ?
               "xml" : 
               "xmlns" : 
             computePrefix(namespace));
      }
      demandRegistry.put(namespace, ePackage);

      // demandDocumentRoot(ePackage);

      EClass documentRootEClass = EcoreFactory.eINSTANCE.createEClass();
      documentRootEClass.getESuperTypes().add(XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot());
      documentRootEClass.setName("DocumentRoot");
      ePackage.getEClassifiers().add(documentRootEClass);
      setDocumentRoot(documentRootEClass);
    }
    return ePackage;
  }
  
  protected String computePrefix(String namespace)
  {
    int index = namespace.length();
    boolean containsLetter = false;
    StringBuilder prefix = new StringBuilder(index);
    while (--index >= 0)
    {
      char character = namespace.charAt(index);
      if (XMLTypeUtil.isNCNamePart(character))
      {
        prefix.append(character);
        containsLetter = Character.isLetter(character);
        break;
      }
    }
    while (--index >= 0)
    {
      char character = namespace.charAt(index);
      if (XMLTypeUtil.isNCNamePart(character))
      {
        prefix.append(character);
        if (!containsLetter)
        {
          containsLetter = Character.isLetter(character);
        }
      }
      else if (!containsLetter)
      {
        prefix.append('_');
      }
      else
      {
        break;
      }
    }
    
    int length = prefix.length();
    if (length == 0 || !XMLTypeUtil.isNCNameStart(prefix.charAt(length - 1)))
    {
      prefix.append('_');
    }
    return prefix.reverse().toString();
  }

  public EClassifier demandType(String namespace, String name)
  {
    EPackage ePackage = demandPackage(namespace);
    EClassifier eClassifier = getType(ePackage, name);
    if (eClassifier != null)
    {
      return eClassifier;
    }
    else
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName(name);
      eClass.getESuperTypes().add(XMLTypePackage.eINSTANCE.getAnyType());
      setContentKind(eClass, MIXED_CONTENT);
      ePackage.getEClassifiers().add(eClass);
      return eClass;
    }
  }

  public EStructuralFeature demandFeature(String namespace, String name, boolean isElement)
  {
    return demandFeature(namespace, name, isElement, isElement);
  }

  public EStructuralFeature demandFeature(String namespace, String name, boolean isElement, boolean isReference)
  {
    EPackage ePackage = demandPackage(namespace);
    EClass documentRootEClass = getDocumentRoot(ePackage);
    EStructuralFeature eStructuralFeature = 
      isElement ? 
        getLocalElement(documentRootEClass, namespace, name) : 
        getLocalAttribute(documentRootEClass, namespace, name);
    if (eStructuralFeature != null)
    {
      return eStructuralFeature;
    }
    else
    {
      if (isReference)
      {
        EReference eReference = EcoreFactory.eINSTANCE.createEReference();
        if (isElement)
        {
          eReference.setContainment(true);
          eReference.setResolveProxies(false);
        }
        eReference.setEType(EcorePackage.Literals.EOBJECT);
        eReference.setName(name);
        eReference.setDerived(true);
        eReference.setTransient(true);
        eReference.setVolatile(true);
        documentRootEClass.getEStructuralFeatures().add(eReference);

        setFeatureKind(eReference, isElement ? ELEMENT_FEATURE : ATTRIBUTE_FEATURE);
        setNamespace(eReference, namespace);

        // Mark the bound as unspecified so that it won't be considered many
        // but can nevertheless be recognized as being unspecified and perhaps still be treat as many.
        //
        if (isElement)
        {
          eReference.setUpperBound(ETypedElement.UNSPECIFIED_MULTIPLICITY);
        }

        return eReference;
      }
      else
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName(name);
        eAttribute.setEType(XMLTypePackage.eINSTANCE.getAnySimpleType());
        eAttribute.setDerived(true);
        eAttribute.setTransient(true);
        eAttribute.setVolatile(true);
        documentRootEClass.getEStructuralFeatures().add(eAttribute);

        setFeatureKind(eAttribute, isElement ? ELEMENT_FEATURE : ATTRIBUTE_FEATURE);
        setNamespace(eAttribute, namespace);

        // Mark the bound as unspecified so that it won't be considered many
        // but can nevertheless be recognized as being unspecified and perhaps still be treat as many.
        //
        if (isElement)
        {
          eAttribute.setUpperBound(ETypedElement.UNSPECIFIED_MULTIPLICITY);
        }

        return eAttribute;
      }
    }
  }

  @SuppressWarnings("unchecked")
  public Collection<EPackage> demandedPackages()
  {
    return (Collection<EPackage>)(Collection<?>)demandRegistry.values();
  }





  public static interface EPackageExtendedMetaData
  {
    interface Holder
    {
      EPackageExtendedMetaData getExtendedMetaData();
      void setExtendedMetaData(EPackageExtendedMetaData ePackageExtendedMetaData);
    }

    boolean isQualified();
    void setQualified(boolean isQualified);

    EClassifier getType(String name);

    void rename(EClassifier eClassifier, String newName);
  }

  public class EPackageExtendedMetaDataImpl implements EPackageExtendedMetaData
  {
    protected EPackage ePackage;
    protected boolean isInitialized;
    protected boolean isQualified;
    protected Map<String, EClassifier> nameToClassifierMap;

    public EPackageExtendedMetaDataImpl(EPackage ePackage)
    {
      this.ePackage = ePackage;
    }

    public boolean isQualified()
    {
      if (!isInitialized)
      {
        setQualified(basicIsQualified(ePackage));
      }
      return isQualified;
    }

    public void setQualified(boolean isQualified)
    {
      this.isQualified = isQualified;
      isInitialized = true;
    }
    
    public EClassifier getType(String name)
    {
      EClassifier result = null;
      if (nameToClassifierMap != null)
      {
        result = nameToClassifierMap.get(name);
      }
      if (result == null)
      {
        List<EClassifier> eClassifiers = ePackage.getEClassifiers();
        int size = eClassifiers.size();
        if (nameToClassifierMap == null || nameToClassifierMap.size() != size)
        {
          Map<String, EClassifier> nameToClassifierMap = new HashMap<String, EClassifier>();
          if (this.nameToClassifierMap != null)
          {
            nameToClassifierMap.putAll(this.nameToClassifierMap);
          }
  
          // For demand created created packages we allow the list of classifiers to grow 
          // so this should handle those additional instances.
          //
          int originalMapSize = nameToClassifierMap.size();
          for (int i = originalMapSize; i < size; ++i)
          {
            EClassifier eClassifier = eClassifiers.get(i);
            String eClassifierName = getName(eClassifier);
            EClassifier conflictingEClassifier = nameToClassifierMap.put(eClassifierName, eClassifier);
            if (conflictingEClassifier != null && conflictingEClassifier != eClassifier)
            {
              nameToClassifierMap.put(eClassifierName, conflictingEClassifier);
            }
          }
  
          if (nameToClassifierMap.size() != size)
          {
            for (int i = 0; i < originalMapSize; ++i)
            {
              EClassifier eClassifier = eClassifiers.get(i);
              String eClassifierName = getName(eClassifier);
              EClassifier conflictingEClassifier = nameToClassifierMap.put(eClassifierName, eClassifier);
              if (conflictingEClassifier != null && conflictingEClassifier != eClassifier)
              {
                nameToClassifierMap.put(eClassifierName, conflictingEClassifier);
              }
            }
          }
          this.nameToClassifierMap = nameToClassifierMap;
        }
        result = nameToClassifierMap.get(name);
      }

      return result;
    }

    public void rename(EClassifier eClassifier, String newName)
    {
      if (nameToClassifierMap != null)
      {
        nameToClassifierMap.values().remove(eClassifier);
        nameToClassifierMap.put(newName, eClassifier);
      }
    }
  }

  protected EPackageExtendedMetaData getExtendedMetaData(EPackage ePackage)
  {
    if (extendedMetaDataHolderCache != null)
    {
      EPackageExtendedMetaData result = (EPackageExtendedMetaData)extendedMetaDataHolderCache.get(ePackage);
      if (result == null)
      {
        extendedMetaDataHolderCache.put(ePackage, result = createEPackageExtendedMetaData(ePackage));
      }
      return result;
    }
    else
    {
      EPackageExtendedMetaData.Holder holder = (EPackageExtendedMetaData.Holder)ePackage;
      EPackageExtendedMetaData result = holder.getExtendedMetaData();
      if (result == null)
      {
        holder.setExtendedMetaData(result = createEPackageExtendedMetaData(ePackage));
      }
      return result;
    } 
  }

  protected EPackageExtendedMetaData createEPackageExtendedMetaData(EPackage ePackage)
  {
    return new EPackageExtendedMetaDataImpl(ePackage);
  }


  protected static final String UNINITIALIZED_STRING = "uninitialized";
  protected static final int UNINITIALIZED_INT = -2;
  protected static final EDataType UNINITIALIZED_EDATA_TYPE = EcoreFactory.eINSTANCE.createEDataType();
  protected static final EStructuralFeature UNINITIALIZED_ESTRUCTURAL_FEATURE = EcoreFactory.eINSTANCE.createEAttribute();


  public static interface EClassifierExtendedMetaData
  {
    interface Holder
    {
      EClassifierExtendedMetaData getExtendedMetaData();
      void setExtendedMetaData(EClassifierExtendedMetaData eClassifierExtendedMetaData);
    }
    String getName();
    void setName(String name);

    int getContentKind();
    void setContentKind(int kind);

    int getDerivationKind();

    EDataType getBaseType();
    void setBaseType(EDataType baseType);

    EDataType getItemType();
    void setItemType(EDataType itemType);

    List<EDataType> getMemberTypes();
    void setMemberTypes(List<EDataType> memberTypes);

    int getWhiteSpaceFacet();
    void setWhiteSpaceFacet(int whiteSpace);

    List<String> getEnumerationFacet();
    void setEnumerationFacet(List<String> literals);

    List<String> getPatternFacet();
    void setPatternFacet(List<String> literals);

    int getTotalDigitsFacet();
    void setTotalDigitsFacet(int digits);

    int getFractionDigitsFacet();
    void setFractionDigitsFacet(int digits);

    int getLengthFacet();
    void setLengthFacet(int length);
    
    int getMinLengthFacet();
    void setMinLengthFacet(int length);
    
    int getMaxLengthFacet();
    void setMaxLengthFacet(int length);

    String getMinExclusiveFacet();
    void setMinExclusiveFacet(String literal);

    String getMaxExclusiveFacet();
    void setMaxExclusiveFacet(String literal);

    String getMinInclusiveFacet();
    void setMinInclusiveFacet(String literal);

    String getMaxInclusiveFacet();
    void setMaxInclusiveFacet(String literal);
  }

  public class EClassExtendedMetaDataImpl implements EClassifierExtendedMetaData
  {
    protected EClass eClass;
    protected String name = UNINITIALIZED_STRING;
    protected int contentKind = UNINITIALIZED_INT;

    public EClassExtendedMetaDataImpl(EClass eClass)
    {
      this.eClass = eClass;
    }

    public String getName()
    {
      if (name == UNINITIALIZED_STRING)
      {
        setName(basicGetName(eClass));
      }
      return name;
    }

    public void setName(String name)
    {
      this.name = name;
    }

    public int getContentKind()
    {
      if (contentKind == UNINITIALIZED_INT)
      {
        setContentKind(basicGetContentKind(eClass));
      }
      return contentKind;
    }

    public void setContentKind(int kind)
    {
      this.contentKind = kind;
    }

    public int getDerivationKind()
    {
      return 0;
    }

    public EDataType getBaseType()
    {
      return null;
    }

    public void setBaseType(EDataType baseType)
    {
      throw new UnsupportedOperationException("Can't set the base type of an EClass");
    }

    public EDataType getItemType()
    {
      return null;
    }

    public void setItemType(EDataType itemType)
    {
      throw new UnsupportedOperationException("Can't set the item type of an EClass");
    }

    public List<EDataType> getMemberTypes()
    {
      return Collections.emptyList();
    }

    public void setMemberTypes(List<EDataType> memberTypes)
    {
      throw new UnsupportedOperationException("Can't set the member types of an EClass");
    }

    public int getWhiteSpaceFacet()
    {
      return 0;
    }

    public void setWhiteSpaceFacet(int whiteSpace)
    {
      throw new UnsupportedOperationException("Can't set the white space of an EClass");
    }

    public List<String> getEnumerationFacet()
    {
      return Collections.emptyList();
    }

    public void setEnumerationFacet(List<String> literals)
    {
      throw new UnsupportedOperationException("Can't set the enumeration of an EClass");
    }

    public List<String> getPatternFacet()
    {
      return Collections.emptyList();
    }

    public void setPatternFacet(List<String> pattern)
    {
      throw new UnsupportedOperationException("Can't set the pattern of an EClass");
    }

    public int getTotalDigitsFacet()
    {
      return -1;
    }

    public void setTotalDigitsFacet(int digits)
    {
      throw new UnsupportedOperationException("Can't set the total digits of an EClass");
    }

    public int getFractionDigitsFacet()
    {
      return -1;
    }

    public void setFractionDigitsFacet(int digits)
    {
      throw new UnsupportedOperationException("Can't set the fraction digits of an EClass");
    }

    public int getLengthFacet()
    {
      return -1;
    }

    public void setLengthFacet(int length)
    {
      throw new UnsupportedOperationException("Can't set the length of an EClass");
    }

    public int getMinLengthFacet()
    {
      return -1;
    }

    public void setMinLengthFacet(int minLength)
    {
      throw new UnsupportedOperationException("Can't set the min length of an EClass");
    }

    public int getMaxLengthFacet()
    {
      return -1;
    }

    public void setMaxLengthFacet(int maxLength)
    {
      throw new UnsupportedOperationException("Can't set the max length of an EClass");
    }

    public String getMinExclusiveFacet()
    {
      return null;
    }

    public void setMinExclusiveFacet(String literal)
    {
      throw new UnsupportedOperationException("Can't set the min exclusive of an EClass");
    }

    public String getMaxExclusiveFacet()
    {
      return null;
    }

    public void setMaxExclusiveFacet(String literal)
    {
      throw new UnsupportedOperationException("Can't set the max exclusive of an EClass");
    }

    public String getMinInclusiveFacet()
    {
      return null;
    }

    public void setMinInclusiveFacet(String literal)
    {
      throw new UnsupportedOperationException("Can't set the min inclusive of an EClass");
    }

    public String getMaxInclusiveFacet()
    {
      return null;
    }

    public void setMaxInclusiveFacet(String literal)
    {
      throw new UnsupportedOperationException("Can't set the max inclusive of an EClass");
    }
  }

  public class EDataTypeExtendedMetaDataImpl implements EClassifierExtendedMetaData
  {
    protected EDataType eDataType;
    protected String name = UNINITIALIZED_STRING;
    protected EDataType baseType = UNINITIALIZED_EDATA_TYPE;
    protected EDataType itemType = UNINITIALIZED_EDATA_TYPE;
    protected List<EDataType> memberTypes;
    protected int derivationKind = UNINITIALIZED_INT;
    protected int whiteSpace = UNINITIALIZED_INT;
    protected List<String> enumerationLiterals;
    protected List<String> pattern;
    int totalDigits = UNINITIALIZED_INT;
    int fractionDigits = UNINITIALIZED_INT;
    int length = UNINITIALIZED_INT;
    int minLength = UNINITIALIZED_INT;
    int maxLength = UNINITIALIZED_INT;
    String minExclusive = UNINITIALIZED_STRING;
    String maxExclusive = UNINITIALIZED_STRING;
    String minInclusive = UNINITIALIZED_STRING;
    String maxInclusive = UNINITIALIZED_STRING;

    public EDataTypeExtendedMetaDataImpl(EDataType eDataType)
    {
      this.eDataType = eDataType;
    }

    public String getName()
    {
      if (name == UNINITIALIZED_STRING)
      {
        setName(basicGetName(eDataType));
      }
      return name;
    }

    public void setName(String name)
    {
      this.name = name;
    }

    public int getContentKind()
    {
      return 0;
    }

    public void setContentKind(int kind)
    {
      throw new UnsupportedOperationException("Can't set the content kind of an EDataType");
    }

    public int getDerivationKind()
    {
      if (derivationKind == UNINITIALIZED_INT)
      {
        if (getBaseType() != null)
        {
          derivationKind = RESTRICTION_DERIVATION;
        }
        else if (getItemType() != null)
        {
          derivationKind = LIST_DERIVATION;
        }
        else if (!getMemberTypes().isEmpty())
        {
          derivationKind = UNION_DERIVATION;
        }
        else
        {
          derivationKind = 0;
        }
      }
      return derivationKind;
    }

    public EDataType getBaseType()
    {
      if (baseType == UNINITIALIZED_EDATA_TYPE)
      {
        setBaseType(basicGetBaseType(eDataType));
      }
      return baseType;
    }

    public void setBaseType(EDataType baseType)
    {
      this.baseType = baseType;
      derivationKind = UNINITIALIZED_INT;
    }

    public EDataType getItemType()
    {
      if (itemType == UNINITIALIZED_EDATA_TYPE)
      {
        setItemType(basicGetItemType(eDataType));
      }
      return itemType;
    }

    public void setItemType(EDataType itemType)
    {
      this.itemType = itemType;
      derivationKind = UNINITIALIZED_INT;
    }

    public List<EDataType> getMemberTypes()
    {
      if (memberTypes == null)
      {
        setMemberTypes(basicGetMemberTypes(eDataType));
      }
      return memberTypes;
    }

    public void setMemberTypes(List<EDataType> memberTypes)
    {
      this.memberTypes = memberTypes;
      derivationKind = UNINITIALIZED_INT;
    }

    public int getWhiteSpaceFacet()
    {
      if (whiteSpace == UNINITIALIZED_INT)
      {
        setWhiteSpaceFacet(basicGetWhiteSpaceFacet(eDataType));
      }
      return whiteSpace;
    }

    public void setWhiteSpaceFacet(int whiteSpace)
    {
      this.whiteSpace = whiteSpace;
    }

    public List<String> getEnumerationFacet()
    {
      if (enumerationLiterals == null)
      {
        setEnumerationFacet(basicGetEnumerationFacet(eDataType));
      }
      return enumerationLiterals;
    }

    public void setEnumerationFacet(List<String> literals)
    {
      this.enumerationLiterals = literals;
    }

    public List<String> getPatternFacet()
    {
      if (pattern == null)
      {
        setPatternFacet(basicGetPatternFacet(eDataType));
      }
      return pattern;
    }

    public void setPatternFacet(List<String> pattern)
    {
      this.pattern = pattern;
    }

    public int getTotalDigitsFacet()
    {
      if (totalDigits == UNINITIALIZED_INT)
      {
        setTotalDigitsFacet(basicGetTotalDigitsFacet(eDataType));
      }
      return totalDigits;
    }

    public void setTotalDigitsFacet(int digits)
    {
      this.totalDigits = digits;
    }

    public int getFractionDigitsFacet()
    {
      if (fractionDigits == UNINITIALIZED_INT)
      {
        setFractionDigitsFacet(basicGetFractionDigitsFacet(eDataType));
      }
      return fractionDigits;
    }

    public void setFractionDigitsFacet(int digits)
    {
      this.fractionDigits = digits;
    }

    public int getLengthFacet()
    {
      if (length == UNINITIALIZED_INT)
      {
        setLengthFacet(basicGetLengthFacet(eDataType));
      }
      return length;
    }

    public void setLengthFacet(int length)
    {
      this.length = length;
    }

    public int getMinLengthFacet()
    {
      if (minLength == UNINITIALIZED_INT)
      {
        setMinLengthFacet(basicGetMinLengthFacet(eDataType));
      }
      return minLength;
    }

    public void setMinLengthFacet(int minLength)
    {
      this.minLength = minLength;
    }

    public int getMaxLengthFacet()
    {
      if (maxLength == UNINITIALIZED_INT)
      {
        setMaxLengthFacet(basicGetMaxLengthFacet(eDataType));
      }
      return maxLength;
    }

    public void setMaxLengthFacet(int maxLength)
    {
      this.maxLength = maxLength;
    }

    public String getMinExclusiveFacet()
    {
      if (minExclusive == UNINITIALIZED_STRING)
      {
        setMinExclusiveFacet(basicGetMinExclusiveFacet(eDataType));
      }
      return minExclusive;
    }

    public void setMinExclusiveFacet(String literal)
    {
      this.minExclusive = literal;
    }

    public String getMaxExclusiveFacet()
    {
      if (maxExclusive == UNINITIALIZED_STRING)
      {
        setMaxExclusiveFacet(basicGetMaxExclusiveFacet(eDataType));
      }
      return maxExclusive;
    }

    public void setMaxExclusiveFacet(String literal)
    {
      this.maxExclusive = literal;
    }

    public String getMinInclusiveFacet()
    {
      if (minInclusive == UNINITIALIZED_STRING)
      {
        setMinInclusiveFacet(basicGetMinInclusiveFacet(eDataType));
      }
      return minInclusive;
    }

    public void setMinInclusiveFacet(String literal)
    {
      this.minInclusive = literal;
    }

    public String getMaxInclusiveFacet()
    {
      if (maxInclusive == UNINITIALIZED_STRING)
      {
        setMaxInclusiveFacet(basicGetMaxInclusiveFacet(eDataType));
      }
      return maxInclusive;
    }

    public void setMaxInclusiveFacet(String literal)
    {
      this.maxInclusive = literal;
    }
  }

  protected EClassifierExtendedMetaData getExtendedMetaData(EClassifier eClassifier)
  {
    if (extendedMetaDataHolderCache != null)
    {
      EClassifierExtendedMetaData result = (EClassifierExtendedMetaData)extendedMetaDataHolderCache.get(eClassifier);
      if (result == null)
      {
        extendedMetaDataHolderCache.put(eClassifier, result = createEClassifierExtendedMetaData(eClassifier));
      }
      return result;
    }
    else
    {
      EClassifierExtendedMetaData.Holder holder = (EClassifierExtendedMetaData.Holder)eClassifier;
      EClassifierExtendedMetaData result = holder.getExtendedMetaData();
      if (result == null)
      {
        holder.setExtendedMetaData(result = createEClassifierExtendedMetaData(eClassifier));
      }
      return result;
    }  
  }

  protected EClassifierExtendedMetaData createEClassifierExtendedMetaData(EClassifier eClassifier)
  {
    if (eClassifier instanceof EClass)
    {
      return new EClassExtendedMetaDataImpl((EClass)eClassifier);
    }
    else
    {
      return new EDataTypeExtendedMetaDataImpl((EDataType)eClassifier);
    }
  }

  public static interface EStructuralFeatureExtendedMetaData
  {
    interface Holder
    {
      EStructuralFeatureExtendedMetaData getExtendedMetaData();
      void setExtendedMetaData(EStructuralFeatureExtendedMetaData eStructuralFeatureExtendedMetaData);
    }

    String getName();
    void setName(String name);

    String getNamespace();
    void setNamespace(String namespace);

    int getFeatureKind();
    void setFeatureKind(int kind);

    List<String> getWildcards();
    void setWildcards(List<String> wildcards);

    int getProcessingKind();
    void setProcessingKind(int kind);

    EStructuralFeature getGroup();
    void setGroup(EStructuralFeature group);

    EStructuralFeature getAffiliation();
    void setAffiliation(EStructuralFeature affiliation);

    Map<EClass, FeatureMapUtil.Validator> getValidatorMap();
  }

  public class EStructuralFeatureExtendedMetaDataImpl implements EStructuralFeatureExtendedMetaData
  {
    protected EStructuralFeature eStructuralFeature;
    protected String name = UNINITIALIZED_STRING;
    protected String namespace = UNINITIALIZED_STRING;
    protected int featureKind = UNINITIALIZED_INT;
    protected List<String> wildcards;
    protected int processingKind = UNINITIALIZED_INT;
    protected EStructuralFeature group = UNINITIALIZED_ESTRUCTURAL_FEATURE;
    protected EStructuralFeature affiliation = UNINITIALIZED_ESTRUCTURAL_FEATURE;
    protected Map<EClass, FeatureMapUtil.Validator> validatorMap;

    public EStructuralFeatureExtendedMetaDataImpl(EStructuralFeature eStructuralFeature)
    {
      this.eStructuralFeature = eStructuralFeature;
    }

    public Map<EClass, FeatureMapUtil.Validator> getValidatorMap()
    {
      if (validatorMap == null)
      {
        validatorMap = new Hashtable<EClass, FeatureMapUtil.Validator>();
      }
      return validatorMap;
    }

    public String getName()
    {
      if (name == UNINITIALIZED_STRING)
      {
        setName(basicGetName(eStructuralFeature));
      }
      return name;
    }

    public void setName(String name)
    {
      this.name= name;
    }

    public String getNamespace()
    {
      if (namespace == UNINITIALIZED_STRING)
      {
        setNamespace(basicGetNamespace(eStructuralFeature));
      }
      return namespace;
    }

    public void setNamespace(String namespace)
    {
      this.namespace = namespace;
    }

    public int getFeatureKind()
    {
      if (featureKind == UNINITIALIZED_INT)
      {
        setFeatureKind(basicGetFeatureKind(eStructuralFeature));
      }
      return featureKind;
    }

    public void setFeatureKind(int kind)
    {
      this.featureKind = kind;
    }

    public List<String> getWildcards()
    {
      if (wildcards == null)
      {
        setWildcards(basicGetWildcards(eStructuralFeature));
      }
      return wildcards;
    }

    public void setWildcards(List<String> wildcards)
    {
      this.wildcards = wildcards;
    }

    public int getProcessingKind()
    {
      if (processingKind == UNINITIALIZED_INT)
      {
        setProcessingKind(basicGetProcessingKind(eStructuralFeature));
      }
      return processingKind;
    }

    public void setProcessingKind(int kind)
    {
      this.processingKind = kind;
    }

    public EStructuralFeature getGroup()
    {
      if (group == UNINITIALIZED_ESTRUCTURAL_FEATURE)
      {
        setGroup(basicGetGroup(eStructuralFeature));
      }
      return group;
    }

    public void setGroup(EStructuralFeature group)
    {
      this.group = group;
    }

    public EStructuralFeature getAffiliation()
    {
      if (affiliation == UNINITIALIZED_ESTRUCTURAL_FEATURE)
      {
        setAffiliation(basicGetAffiliation(eStructuralFeature));
      }
      return affiliation;
    }

    public void setAffiliation(EStructuralFeature affiliation)
    {
      this.affiliation = affiliation;
    }
  }

  protected EStructuralFeatureExtendedMetaData getExtendedMetaData(EStructuralFeature eStructuralFeature)
  {
    if (extendedMetaDataHolderCache != null)
    {
      EStructuralFeatureExtendedMetaData result = (EStructuralFeatureExtendedMetaData)extendedMetaDataHolderCache.get(eStructuralFeature);
      if (result == null)
      {
        extendedMetaDataHolderCache.put(eStructuralFeature, result = createEStructuralFeatureExtendedMetaData(eStructuralFeature));
      }
      return result;
    }
    else
    {
      EStructuralFeatureExtendedMetaData.Holder holder = (EStructuralFeatureExtendedMetaData.Holder)eStructuralFeature;
      EStructuralFeatureExtendedMetaData result = holder.getExtendedMetaData();
      if (result == null)
      {
        holder.setExtendedMetaData(result = createEStructuralFeatureExtendedMetaData(eStructuralFeature));
      }
      return result;
    }
  }

  protected EStructuralFeatureExtendedMetaData createEStructuralFeatureExtendedMetaData(EStructuralFeature eStructuralFeature)
  {
    return new EStructuralFeatureExtendedMetaDataImpl(eStructuralFeature);
  }

  private static String replace(String in, String oldString, String newString)
  {
    if (in == null || oldString == null)
    {
      return in;
    }
      
    int oldStringLength = oldString.length();
    if (oldStringLength == 0)
    {
      return in;
    }
    
    if (newString == null)
    {
      newString = "";
    }
    int newStringLength = newString.length();
      
    int index = -newStringLength;
    StringBuffer result = new StringBuffer(in);
    while((index = indexOf(result, oldString, index + newStringLength)) >= 0)
    {
      result.replace(index, index + oldStringLength, newString);
    }
    
    return result.toString();
  }
  
  private static int indexOf(StringBuffer in, String str, int fromIndex)
  {
    if (in == null)
    {
      return -1;
    }
    
    if (str == null)
    {
      str = "";
    }
      
    int lengthIn = in.length();
    int lengthStr = str.length();

    if (lengthIn < lengthStr)
    {
      return -1;
    }

    if (fromIndex > lengthIn)
    {
      if (lengthIn == 0 && fromIndex == 0 && lengthStr == 0)
      {
        return 0;
      }
      return -1;
    }
    
    if (fromIndex < 0)
    {
      fromIndex = 0;
    }
      
    if (lengthStr == 0)
    {
      return fromIndex;
    }
      
    int strPos = 0;
    for (int i = fromIndex; i < lengthIn; i++)
    {
      if (in.charAt(i) == str.charAt(strPos))
      {
        strPos++;
        if(strPos == lengthStr)
        {
          return i - lengthStr + 1;
        }
      }
      else
      {
        strPos = 0; 
      }
    }
    
    return -1;
  }
}
