/**
 * Copyright (c) 2002-2008 IBM Corporation, Embarcadero Technologies, 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
 *   Kenn Hussey (Embarcadero Technologies) - 218759
 */
package org.eclipse.emf.ecore.xmi.impl;


import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
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.EcorePackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
import org.eclipse.emf.ecore.util.BasicExtendedMetaData;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.ecore.xmi.ClassNotFoundException;
import org.eclipse.emf.ecore.xmi.EcoreBuilder;
import org.eclipse.emf.ecore.xmi.FeatureNotFoundException;
import org.eclipse.emf.ecore.xmi.IllegalValueException;
import org.eclipse.emf.ecore.xmi.PackageNotFoundException;
import org.eclipse.emf.ecore.xmi.UnresolvedReferenceException;
import org.eclipse.emf.ecore.xmi.XMIException;
import org.eclipse.emf.ecore.xmi.XMIPlugin;
import org.eclipse.emf.ecore.xmi.XMLDefaultHandler;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLOptions;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.util.DefaultEcoreBuilder;
import org.eclipse.emf.ecore.xml.type.AnyType;
import org.eclipse.emf.ecore.xml.type.SimpleAnyType;
import org.eclipse.emf.ecore.xml.type.XMLTypeFactory;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.eclipse.emf.ecore.xml.type.util.XMLTypeUtil;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.Locator2;
import org.xml.sax.helpers.DefaultHandler;


/**
 * This class is a generic interface for loading XML files and
 * creating EObjects from them. Its subclasses include the SAXXMLHandler
 * class, which wraps this class in a SAX default handler.
 */
public abstract class XMLHandler extends DefaultHandler implements XMLDefaultHandler
{
  protected static final String ERROR_TYPE = "error";
  protected static final String OBJECT_TYPE = "object";
  protected static final String UNKNOWN_FEATURE_TYPE = "unknownFeature";
  protected static final String DOCUMENT_ROOT_TYPE = "documentRoot";

  protected final static String TYPE_ATTRIB = XMLResource.XSI_NS + ":" + XMLResource.TYPE;
  protected final static String NIL_ATTRIB = XMLResource.XSI_NS + ":" + XMLResource.NIL;
  protected final static String SCHEMA_LOCATION_ATTRIB = XMLResource.XSI_NS + ":" + XMLResource.SCHEMA_LOCATION;
  protected final static String NO_NAMESPACE_SCHEMA_LOCATION_ATTRIB = XMLResource.XSI_NS + ":" + XMLResource.NO_NAMESPACE_SCHEMA_LOCATION;

  protected final static boolean DEBUG_DEMANDED_PACKAGES = false;

  protected static class MyStack<E> extends BasicEList<E>
  {
    private static final long serialVersionUID = 1L;

    public MyStack()
    {
      super();
    }

    @SuppressWarnings("unchecked")
    public final E peek()
    {
      return size == 0 ? null : (E)data[size - 1];
    }

    public final void push(E o)
    {
      grow(size + 1);
      data[size++] = o;
    }

    @SuppressWarnings("unchecked")
    public final E pop()
    {
      return size == 0 ?  null : (E)data[--size];
    }
  }

  protected static class MyEObjectStack extends MyStack<EObject>
  {
    private static final long serialVersionUID = 1L;

    protected EObject [] eObjectData;

    public MyEObjectStack()
    {
      super();
    }

    @Override
    protected final Object[] newData(int capacity)
    {
      return eObjectData = new EObject[capacity];
    }

    public final EObject peekEObject()
    {
      return size == 0 ? null : eObjectData[size - 1];
    }

    public final EObject popEObject()
    {
      return size == 0 ?  null : eObjectData[--size];
    }

    @Override
    public void clear()
    {
      eObjectData = null;
      super.clear();
    }
  }

  /**
   * For unresolved forward references, the line number where the incorrect id
   * appeared in an XML resource is needed, so the Value for the forward reference
   * and the line number where the forward reference occurred must be saved until
   * the end of the XML resource is encountered.
   */
  protected static class SingleReference
  {
    private EObject object;
    private EStructuralFeature feature;
    private Object value;
    private int position;
    private int lineNumber;
    private int columnNumber;

    public SingleReference(EObject object,
                    EStructuralFeature feature,
                    Object value,
                    int position,
                    int lineNumber,
                    int columnNumber)
    {
      this.object       = object;
      this.feature      = feature;
      this.value        = value;
      this.position     = position;
      this.lineNumber   = lineNumber;
      this.columnNumber = columnNumber;
    }

    public EObject getObject()
    {
      return object;
    }

    public EStructuralFeature getFeature()
    {
      return feature;
    }

    public Object getValue()
    {
      return value;
    }

    public int getPosition()
    {
      return position;
    }

    public int getLineNumber()
    {
      return lineNumber;
    }

    public int getColumnNumber()
    {
      return columnNumber;
    }
  }

  protected static class ManyReference implements XMLHelper.ManyReference
  {
    private EObject object;
    private EStructuralFeature feature;
    private Object[] values;
    private int[] positions;
    private int lineNumber;
    private int columnNumber;

    public ManyReference(EObject object,
                  EStructuralFeature feature,
                  Object[] values,
                  int[] positions,
                  int lineNumber,
                  int columnNumber)
    {
      this.object       = object;
      this.feature      = feature;
      this.values       = values;
      this.positions    = positions;
      this.lineNumber   = lineNumber;
      this.columnNumber = columnNumber;
    }

    public EObject getObject()
    {
      return object;
    }

    public EStructuralFeature getFeature()
    {
      return feature;
    }

    public Object[] getValues()
    {
      return values;
    }

    public int[] getPositions()
    {
      return positions;
    }

    public int getLineNumber()
    {
      return lineNumber;
    }

    public int getColumnNumber()
    {
      return columnNumber;
    }
  }

  protected XMLResource xmlResource;
  protected XMLHelper helper;
  protected MyStack<String> elements;
  protected MyEObjectStack objects;
  protected MyStack<Object> types;
  protected MyStack<FeatureMap> mixedTargets;
  protected Map<String, EFactory> prefixesToFactories;
  protected Map<String, URI> urisToLocations;
  protected Map<String, URI> externalURIToLocations;
  protected boolean processSchemaLocations;
  protected InternalEList<EObject> extent;
  protected List<EObject> deferredExtent;
  protected ResourceSet resourceSet;
  protected EPackage.Registry packageRegistry;
  protected URI resourceURI;
  protected boolean resolve;
  protected boolean oldStyleProxyURIs;
  protected boolean disableNotify;
  protected StringBuffer text;
  protected boolean isIDREF;
  protected boolean isSimpleFeature;
  protected List<InternalEObject> sameDocumentProxies;
  protected List<SingleReference> forwardSingleReferences;
  protected List<ManyReference> forwardManyReferences;
  protected Object[] identifiers;
  protected int[] positions;
  protected static final int ARRAY_SIZE = 64;
  protected static final int REFERENCE_THRESHOLD = 5;
  protected int capacity;
  protected Set<String> notFeatures;
  protected String idAttribute;
  protected String hrefAttribute;
  protected XMLResource.XMLMap xmlMap;
  protected ExtendedMetaData extendedMetaData;
  protected EClass anyType;
  protected EClass anySimpleType;
  protected boolean recordUnknownFeature;
  protected boolean useNewMethods;
  protected boolean recordAnyTypeNSDecls;
  protected Map<EObject, AnyType> eObjectToExtensionMap;
  protected EStructuralFeature contextFeature;
  protected EPackage xmlSchemaTypePackage = XMLTypePackage.eINSTANCE;
  protected boolean deferIDREFResolution;
  protected boolean processAnyXML;
  protected EcoreBuilder ecoreBuilder;
  protected boolean isRoot;
  protected Locator locator;
  protected Attributes attribs;
  protected Map<EStructuralFeature, Integer> featuresToKinds;
  protected boolean useConfigurationCache;
  protected boolean needsPushContext;
  protected XMLResource.ResourceEntityHandler resourceEntityHandler;
  protected XMLResource.URIHandler uriHandler;
  protected EObject documentRoot;
  protected boolean usedNullNamespacePackage;
  protected boolean isNamespaceAware;
  protected boolean suppressDocumentRoot;
  protected boolean laxWildcardProcessing;
  /**
   * @since 2.9
   */
  protected boolean usePackageNsURIAsLocation;

  /**
   * @since 2.9
   */
  protected XMLResource.MissingPackageHandler missingPackageHandler;

  /**
   */
  public XMLHandler(XMLResource xmlResource, XMLHelper helper, Map<?, ?> options)
  {
    this.xmlResource = xmlResource;
    this.helper = helper;
    elements = new MyStack<String>();
    objects  = new MyEObjectStack();
    mixedTargets = new MyStack<FeatureMap>();

    types    = new MyStack<Object>();
    prefixesToFactories = new HashMap<String, EFactory>();
    forwardSingleReferences = new ArrayList<SingleReference>();
    forwardManyReferences   = new ArrayList<ManyReference>();
    sameDocumentProxies = new ArrayList<InternalEObject>();
    identifiers = new Object[ARRAY_SIZE];
    positions   = new int[ARRAY_SIZE];
    capacity    = ARRAY_SIZE;
    resourceSet  = xmlResource.getResourceSet();
    packageRegistry = resourceSet == null ? EPackage.Registry.INSTANCE : resourceSet.getPackageRegistry();
    resourceURI  = xmlResource.getURI();
    extent       = (InternalEList<EObject>)xmlResource.getContents();
    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_DEFER_ATTACHMENT)))
    {
      deferredExtent = new ArrayList<EObject>();
    }
    resolve      = resourceURI != null && resourceURI.isHierarchical() && !resourceURI.isRelative();

    eObjectToExtensionMap = xmlResource.getEObjectToExtensionMap();
    eObjectToExtensionMap.clear();

    helper.setOptions(options);

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_DISABLE_NOTIFY)))
      disableNotify = true;

    notFeatures = new HashSet<String>();
    notFeatures.add(TYPE_ATTRIB);
    notFeatures.add(SCHEMA_LOCATION_ATTRIB);
    notFeatures.add(NO_NAMESPACE_SCHEMA_LOCATION_ATTRIB);

    xmlMap = (XMLResource.XMLMap) options.get(XMLResource.OPTION_XML_MAP);
    helper.setXMLMap(xmlMap);
    if (xmlMap != null)
    {
      idAttribute = xmlMap.getIDAttributeName();
    }

    Object extendedMetaDataOption = options.get(XMLResource.OPTION_EXTENDED_META_DATA);
    setExtendedMetaDataOption(extendedMetaDataOption);

    recordUnknownFeature = Boolean.TRUE.equals(options.get(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE));
    if (recordUnknownFeature && extendedMetaData == null)
    {
      setExtendedMetaDataOption(Boolean.TRUE);
    }

    useNewMethods = Boolean.FALSE.equals(options.get(XMLResource.OPTION_USE_DEPRECATED_METHODS));

    XMLOptions xmlOptions = (XMLOptions)options.get(XMLResource.OPTION_XML_OPTIONS);
    if (xmlOptions != null)
    {
      processSchemaLocations = xmlOptions.isProcessSchemaLocations();
      externalURIToLocations = xmlOptions.getExternalSchemaLocations();

      if (processSchemaLocations || externalURIToLocations != null)
      {
        if (extendedMetaData == null)
        {
          setExtendedMetaDataOption(Boolean.TRUE);
        }
        ecoreBuilder = xmlOptions.getEcoreBuilder();
        if (ecoreBuilder == null)
        {
          ecoreBuilder = createEcoreBuilder(options, extendedMetaData);
        }
        else
        {
          ecoreBuilder.setExtendedMetaData(extendedMetaData);
        }
      }
      processAnyXML = xmlOptions.isProcessAnyXML();
      if (processAnyXML && extendedMetaData == null)
      {
        setExtendedMetaDataOption(Boolean.TRUE);
      }
    }

    if (extendedMetaData != null)
    {
      AnyType anyType = XMLTypeFactory.eINSTANCE.createAnyType();
      mixedTargets.push(anyType.getMixed());
      text = new StringBuffer();
    }

    anyType = (EClass)options.get(XMLResource.OPTION_ANY_TYPE);
    anySimpleType = (EClass)options.get(XMLResource.OPTION_ANY_SIMPLE_TYPE);

    if (anyType == null)
    {
      anyType = XMLTypePackage.eINSTANCE.getAnyType();
      anySimpleType = XMLTypePackage.eINSTANCE.getSimpleAnyType();
    }

    helper.setAnySimpleType(anySimpleType);

    @SuppressWarnings("unchecked") Map<EClassFeatureNamePair, EStructuralFeature> newEClassFeatureNamePairToEStructuralFeatureMap =
      (Map<EClassFeatureNamePair, EStructuralFeature>)options.get(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP);
    eClassFeatureNamePairToEStructuralFeatureMap =  newEClassFeatureNamePairToEStructuralFeatureMap;
    if (eClassFeatureNamePairToEStructuralFeatureMap == null)
    {
      eClassFeatureNamePairToEStructuralFeatureMap = new HashMap<EClassFeatureNamePair, EStructuralFeature>();
    }
    else
    {
      isOptionUseXMLNameToFeatureSet = true;
    }

    recordAnyTypeNSDecls = Boolean.TRUE.equals(options.get(XMLResource.OPTION_RECORD_ANY_TYPE_NAMESPACE_DECLARATIONS));

    hrefAttribute = XMLResource.HREF;

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE)))
    {
      hrefAttribute = null;
    }

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_DEFER_IDREF_RESOLUTION)))
    {
      helper.setCheckForDuplicates(deferIDREFResolution = true);
    }

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_CONFIGURATION_CACHE)))
    {
      useConfigurationCache = true;
    }

    // The entity handler is the best place to resolve and deresolve URIs since it can do it there just once to produce the entity.
    // So most often the entity handler will be a URI handler as well and when used as a URI handler will be an identity handler.
    //
    uriHandler = (XMLResource.URIHandler)options.get(XMLResource.OPTION_URI_HANDLER);
    resourceEntityHandler = (XMLResource.ResourceEntityHandler)options.get(XMLResource.OPTION_RESOURCE_ENTITY_HANDLER);
    if (resourceEntityHandler != null)
    {
      resourceEntityHandler.reset();
      if (uriHandler == null && resourceEntityHandler instanceof XMLResource.URIHandler)
      {
        uriHandler = (XMLResource.URIHandler)resourceEntityHandler;
        uriHandler.setBaseURI(resourceURI);
      }
    }

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_SUPPRESS_DOCUMENT_ROOT)))
    {
      suppressDocumentRoot = true;
    }

    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_LAX_WILDCARD_PROCESSING)))
    {
      laxWildcardProcessing = true;
    }

    usePackageNsURIAsLocation = !Boolean.FALSE.equals(options.get(XMLResource.OPTION_USE_PACKAGE_NS_URI_AS_LOCATION));
    missingPackageHandler = (XMLResource.MissingPackageHandler)options.get(XMLResource.OPTION_MISSING_PACKAGE_HANDLER);
  }

  protected void setExtendedMetaDataOption(Object extendedMetaDataOption)
  {
    if (extendedMetaDataOption instanceof Boolean)
    {
      if (extendedMetaDataOption.equals(Boolean.TRUE))
      {
        extendedMetaData =
          resourceSet == null ?
            ExtendedMetaData.INSTANCE :
            new BasicExtendedMetaData(resourceSet.getPackageRegistry());
        if (xmlResource != null)
        {
          xmlResource.getDefaultSaveOptions().put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData);
        }
      }
      else
      {
        extendedMetaData = null;
      }
    }
    else
    {
      extendedMetaData = (ExtendedMetaData)extendedMetaDataOption;
    }

    helper.setExtendedMetaData(extendedMetaData);
  }

  public void prepare(XMLResource resource, XMLHelper helper, Map<?, ?> options)
  {
    this.xmlResource = resource;
    this.helper = helper;
    resourceSet = xmlResource.getResourceSet();
    packageRegistry = resourceSet == null ? EPackage.Registry.INSTANCE : resourceSet.getPackageRegistry();
    resourceURI = xmlResource.getURI();
    extent = (InternalEList<EObject>)xmlResource.getContents();
    if (Boolean.TRUE.equals(options.get(XMLResource.OPTION_DEFER_ATTACHMENT)))
    {
      deferredExtent = new ArrayList<EObject>();
    }
    resolve = resourceURI != null && resourceURI.isHierarchical() && !resourceURI.isRelative();
    eObjectToExtensionMap = xmlResource.getEObjectToExtensionMap();
    eObjectToExtensionMap.clear();
    setExtendedMetaDataOption(options.get(XMLResource.OPTION_EXTENDED_META_DATA));
    helper.setOptions(options);
    if (extendedMetaData != null)
    {
      if (ecoreBuilder != null)
      {
        ecoreBuilder.setExtendedMetaData(extendedMetaData);
      }
      AnyType anyType = XMLTypeFactory.eINSTANCE.createAnyType();
      mixedTargets.push(anyType.getMixed());
      text = new StringBuffer();
    }

    // bug #126072
    @SuppressWarnings("unchecked") Map<EClassFeatureNamePair, EStructuralFeature> newEClassFeatureNamePairToEStructuralFeatureMap =
      (Map<EClassFeatureNamePair, EStructuralFeature>)options.get(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP);
    eClassFeatureNamePairToEStructuralFeatureMap =
      newEClassFeatureNamePairToEStructuralFeatureMap;
    if (eClassFeatureNamePairToEStructuralFeatureMap == null)
    {
      eClassFeatureNamePairToEStructuralFeatureMap = new HashMap<EClassFeatureNamePair, EStructuralFeature>();
      isOptionUseXMLNameToFeatureSet = false;
    }
    else
    {
      isOptionUseXMLNameToFeatureSet = true;
      if (helper instanceof XMLHelperImpl && featuresToKinds != null)
      {
        ((XMLHelperImpl)helper).featuresToKinds = featuresToKinds;
      }
    }

    // The entity handler is the best place to resolve and deresolve URIs since it can do it there just once to produce the entity.
    // So most often the entity handler will be a URI handler as well and when used as a URI handler will be an identity handler.
    //
    uriHandler = (XMLResource.URIHandler)options.get(XMLResource.OPTION_URI_HANDLER);
    resourceEntityHandler = (XMLResource.ResourceEntityHandler)options.get(XMLResource.OPTION_RESOURCE_ENTITY_HANDLER);
    if (resourceEntityHandler != null)
    {
      resourceEntityHandler.reset();
      if (uriHandler == null && resourceEntityHandler instanceof XMLResource.URIHandler)
      {
        uriHandler = (XMLResource.URIHandler)resourceEntityHandler;
        uriHandler.setBaseURI(resourceURI);
      }
    }
  }

  public void reset()
  {
    this.xmlResource = null;
    this.extendedMetaData = null;
    // bug #126072
    eClassFeatureNamePair.eClass = null;
    eClassFeatureNamePairToEStructuralFeatureMap = null;
    if (isOptionUseXMLNameToFeatureSet && helper instanceof XMLHelperImpl)
    {
      featuresToKinds = ((XMLHelperImpl)helper).featuresToKinds;
    }
    else
    {
      featuresToKinds = null;
    }

    if (ecoreBuilder != null)
    {
        this.ecoreBuilder.setExtendedMetaData(null);
    }
    this.helper = null;
    elements.clear();
    objects.clear();
    mixedTargets.clear();
    contextFeature = null;
    eObjectToExtensionMap = null;
    // external schema locations should only be processed once, i.e. in the subsequent parse
    // there is no need to process those again.
    externalURIToLocations = null;

    types.clear();
    prefixesToFactories.clear();
    forwardSingleReferences.clear();
    forwardManyReferences.clear();
    sameDocumentProxies.clear();
    for (int i = 0; i < identifiers.length; i++)
    {
      identifiers[i] = null;
    }
    for (int i = 0; i < positions.length; i++)
    {
      positions[i] = 0;
    }
    capacity = ARRAY_SIZE;
    resourceSet = null;
    packageRegistry = null;
    resourceURI = null;
    extent = null;
    deferredExtent = null;
    attribs = null;
    locator = null;
    urisToLocations = null;
    resourceEntityHandler = null;
    uriHandler = null;
    documentRoot = null;
    usedNullNamespacePackage = false;
    isNamespaceAware = false;
  }

  //
  // Overwrite DefaultHandler methods
  //

  @Override
  public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
  {
    // Do nothing.
  }

  @Override
  public void skippedEntity(String name) throws SAXException
  {
    // Do nothing.
  }

  protected XMIException toXMIException(SAXParseException e)
  {
    XMIException xmiException =
      new XMIException
        (e.getException() == null ? e : e.getException(),
         e.getSystemId() == null ? getLocation() : e.getSystemId(),
         e.getLineNumber(),
         e.getColumnNumber());
    return xmiException;
  }

  @Override
  public void warning(SAXParseException e) throws SAXException
  {
    warning(toXMIException(e));
  }

  @Override
  public void error(SAXParseException e) throws SAXException
  {
    error(toXMIException(e));
  }

  @Override
  public void fatalError(SAXParseException e) throws SAXException
  {
    fatalError(toXMIException(e));
    throw e;
  }

  @Override
  public void setDocumentLocator(Locator locator)
  {
    setLocator(locator);
  }

  @Override
  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
  {
    setAttributes(attributes);
    startElement(uri, localName, qName);
  }

  //
  // Implement LexicalHandler methods
  //

  public void startEntity(java.lang.String name)
  {
    if (resourceEntityHandler != null)
    {
      text = new StringBuffer();
    }
  }

  public void endEntity(java.lang.String name)
  {
    if (resourceEntityHandler != null)
    {
      resourceEntityHandler.handleEntity(name, text.toString());
    }
  }

  public void comment(char[] ch, int start, int length) // throws SAXException
  {
    if (mixedTargets.peek() != null)
    {
      if (text != null)
      {
        handleMixedText();
      }

      handleComment(new String(ch, start, length));
    }
  }

  public void startCDATA()
  {
    if (mixedTargets.peek() != null)
    {
      if (text != null)
      {
        handleMixedText();
      }
      text = new StringBuffer();
    }
  }

  public void endCDATA()
  {
    if (mixedTargets.peek() != null && text != null)
    {
      handleCDATA();
    }
  }

  //
  // Implement DTDHandler methods
  //
  public void startDTD(String name, String publicId, String systemId)
  {
    xmlResource.setDoctypeInfo(publicId, systemId);
  }

  public void endDTD()
  {
    // Do nothing.
  }

  @Override
  public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException
  {
    // Do nothing.
  }

  @Override
  public void notationDecl(String name, String publicId, String systemId) throws SAXException
  {
    // Do nothing.
  }

  //
  // Implement EntityResolver methods
  //
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException
  {
    try
    {
      Map<Object, Object> options = new HashMap<Object, Object>();
      options.put("publicId", publicId);
      options.put("systemId", systemId);
      options.put("baseLocation", resourceURI == null ? null : resourceURI.toString());
      URI uri = URI.createURI(systemId);
      if (resolve && uri.isRelative() && uri.hasRelativePath())
      {
        uri = helper.resolve(uri, resourceURI);
      }
      InputStream inputStream = getURIConverter().createInputStream(uri, options);
      InputSource result = new InputSource(inputStream);
      result.setPublicId(publicId);
      result.setSystemId(systemId);
      return result;
    }
    catch (IOException exception)
    {
      throw new SAXException(exception);
    }
  }

  /**
   * Returns the xsi type attribute's value.
   */
  protected abstract String getXSIType();

  /**
   * Process the XML attributes for the newly created object.
   */
  protected abstract void handleObjectAttribs(EObject obj);

  /**
   * Process the XML namespace declarations.
   * @deprecated since 2.2
   */
  @Deprecated
  protected void handleNamespaceAttribs()
  {
    for (int i = 0, size = attribs.getLength(); i < size; ++i)
    {
      String attrib = attribs.getQName(i);
      if (attrib.startsWith(XMLResource.XML_NS))
      {
        handleXMLNSAttribute(attrib, attribs.getValue(i));
      }
      else if (SCHEMA_LOCATION_ATTRIB.equals(attrib))
      {
        handleXSISchemaLocation(attribs.getValue(i));
      }
      else if (NO_NAMESPACE_SCHEMA_LOCATION_ATTRIB.equals(attrib))
      {
        handleXSINoNamespaceSchemaLocation(attribs.getValue(i));
      }
    }
  }

  protected void handleSchemaLocation()
  {
    String xsiSchemLocation = attribs.getValue(ExtendedMetaData.XSI_URI, XMLResource.SCHEMA_LOCATION);
    if (xsiSchemLocation != null)
    {
      handleXSISchemaLocation(xsiSchemLocation);
    }

    String xsiNoNamespaceSchemLocation = attribs.getValue(ExtendedMetaData.XSI_URI, XMLResource.NO_NAMESPACE_SCHEMA_LOCATION);
    if (xsiNoNamespaceSchemLocation != null)
    {
      handleXSINoNamespaceSchemaLocation(xsiNoNamespaceSchemLocation);
    }
  }

  /**
   * Returns true if the xsi:nil attribute is in the list of attributes.
   */
  protected boolean isNull()
  {
    String value = isNamespaceAware ? attribs.getValue(ExtendedMetaData.XSI_URI, XMLResource.NIL) : attribs.getValue(NIL_ATTRIB);
    if (value != null)
    {
      try
      {
        return XMLTypeFactory.eINSTANCE.createBoolean(value);
      }
      catch (RuntimeException exception)
      {
        error(new XMIException(exception));
      }
    }
    return false;
  }

  /**
   * Sets the current attributes and returns the old ones.
   */
  protected Object setAttributes(Object attributes)
  {
    Object oldAttribs = attribs;
    this.attribs = (Attributes)attributes;
    return oldAttribs;
  }

  /**
   * Sets the object that might be used for determining the line and
   * column number.
   */
  protected void setLocator(Object locator)
  {
    this.locator = (Locator)locator;
  }

  protected void recordHeaderInformation()
  {
    if (locator != null)
    {
      if (locator instanceof Locator2)
      {
        Locator2 locator2 = (Locator2)locator;
        String encoding = locator2.getEncoding();
        if (encoding != null)
        {
          this.xmlResource.setEncoding(encoding);
        }
        String version = locator2.getXMLVersion();
        if (version != null)
        {
          this.xmlResource.setXMLVersion(version);
        }
      }
      else
      {
        Class<?> locatorClass = locator.getClass();
        try
        {
          Method encodingMethod = locatorClass.getMethod("getEncoding");
          String encoding = (String)encodingMethod.invoke(locator);
          if (encoding != null)
          {
            this.xmlResource.setEncoding(encoding);
          }
  
          Method versionMethod = locatorClass.getMethod("getXMLVersion");
          String version = (String)versionMethod.invoke(locator);
          if (version != null)
          {
            this.xmlResource.setXMLVersion(version);
          }
        }
        catch (NoSuchMethodException e)
        {
          // Ignore.
        }
        catch (IllegalAccessException e)
        {
          // Ignore.
        }
        catch (InvocationTargetException e)
        {
          // Ignore.
        }
      }
    }
  }

  @Override
  public void startDocument()
  {
    isRoot = true;
    helper.pushContext();
    needsPushContext = true;
  }

  /**
   * This method determines whether to make an object or not, then makes an
   * object based on the XML attributes and the metamodel.
   */
  public void startElement(String uri, String localName, String name)
  {
    if (needsPushContext)
    {
      helper.pushContext();
    }
    needsPushContext = true;
    if (text != null && text.length() > 0)
    {
      if (mixedTargets.peek() != null)
      {
        handleMixedText();
      }
      else
      {
        text = null;
      }
    }

    elements.push(name);
    String prefix = "";

    if (useNewMethods)
    {
      if (isRoot)
      {
        handleSchemaLocation();
      }
      prefix = helper.getPrefix((uri.length() == 0) ? null : uri);
      prefix = (prefix == null) ? "" : prefix;
    }
    else
    {
      handleNamespaceAttribs();
      int index = name.indexOf(':', 0);
      localName = name;
      if (index != -1)
      {
        prefix = name.substring(0, index);
        localName = name.substring(index + 1);
      }
    }
    processElement(name, prefix, localName);
  }

  protected void processElement(String name, String prefix, String localName)
  {
    if (isRoot)
    {
      isRoot = false;
      recordHeaderInformation();
    }
    if (isError())
    {
      types.push(ERROR_TYPE);
    }
    else
    {
      if (objects.isEmpty())
      {
        createTopObject(prefix, localName);
      }
      else
      {
        handleFeature(prefix, localName);
      }
    }
  }

  protected void handleForwardReferences()
  {
    handleForwardReferences(false);
  }

  /**
   * Check if the values of the forward references have been set (they may
   * have been set due to a bidirectional reference being set).  If not, set them.
   * If this is called during end document processing, errors should be diagnosed.
   * If it is called in the middle of a document,
   * we need to clean up the forward reference lists to avoid processing resolved references again later.
   */
  protected void handleForwardReferences(boolean isEndDocument)
  {
    // Handle the same document proxies, which may have problems resulting from the
    // other end of a bidirectional reference being handled as an IDREF rather than as a proxy.
    // When we are done with these, we know that funny proxies are now resolved as if they were handled as IDREFs.
    //
    for (Iterator<InternalEObject> i = sameDocumentProxies.iterator(); i.hasNext(); )
    {
      InternalEObject proxy = i.next();

      // Look through all the references...
      //
      LOOP:
      for (EReference eReference : proxy.eClass().getEAllReferences())
      {
        // And find the one that holds this proxy.
        //
        EReference oppositeEReference = eReference.getEOpposite();
        if (oppositeEReference != null && oppositeEReference.isChangeable() && proxy.eIsSet(eReference))
        {
          // Try to resolve the proxy locally.
          //
          EObject resolvedEObject = null;
          
          try
          {
            resolvedEObject = xmlResource.getEObject(proxy.eProxyURI().fragment());
          }
          catch (RuntimeException exception)
          {
            // Ignore exceptions, e.g., when a URI fragment is used but is ill-formed.
          }
          
          if (resolvedEObject != null)
          {
            // We won't need to process this again later.
            //
            if (!isEndDocument)
            {
              i.remove();
            }

            // Compute the holder of the proxy
            //
            EObject proxyHolder = (EObject)(eReference.isMany() ? ((List<?>)proxy.eGet(eReference)).get(0) : proxy.eGet(eReference));

            // If the proxy holder can hold many values,
            // it may contain a duplicate that resulted when the other end was processed as an IDREF
            // and hence did both sides of the bidirectional relation.
            //
            if (oppositeEReference.isMany())
            {
              // So if the resolved object is also present...
              //
              InternalEList<?> holderContents = (InternalEList<?>)proxyHolder.eGet(oppositeEReference);
              int resolvedEObjectIndex = holderContents.basicIndexOf(resolvedEObject);
              if (resolvedEObjectIndex != -1)
              {
                // Move the resolved object to the right place, remove the proxy, and we're done.
                //
                int proxyIndex = holderContents.basicIndexOf(proxy);
                holderContents.move(proxyIndex, resolvedEObjectIndex);
                holderContents.remove(proxyIndex > resolvedEObjectIndex ? proxyIndex  - 1 : proxyIndex + 1);
                break LOOP;
              }
            }

            if (!oppositeEReference.getEType().isInstance(resolvedEObject))
            {
              error
                (new IllegalValueException
                   (proxyHolder,
                    oppositeEReference,
                    resolvedEObject,
                    null,
                    getLocation(),
                    getLineNumber(),
                    getColumnNumber()));
              break LOOP;
            }

            // If the resolved object doesn't contain a reference to the proxy holder as it should.
            //
            if (eReference.isMany() ?
                  !((InternalEList<?>)resolvedEObject.eGet(eReference)).basicContains(proxyHolder) :
                  resolvedEObject.eGet(eReference) != proxyHolder)
            {
              // The proxy needs to be replaced in a way that updates both ends of the reference.
              //
              if (oppositeEReference.isMany())
              {
                @SuppressWarnings("unchecked") InternalEList<EObject> proxyHolderList = (InternalEList<EObject>)proxyHolder.eGet(oppositeEReference);
                proxyHolderList.setUnique(proxyHolderList.basicIndexOf(proxy), resolvedEObject);
              }
              else
              {
                proxyHolder.eSet(oppositeEReference, resolvedEObject);
              }
            }
          }

          break;
        }
      }
    }

    for (Iterator<SingleReference> i = forwardSingleReferences.iterator(); i.hasNext(); )
    {
      SingleReference ref = i.next();
      EObject obj = null;
      RuntimeException cause = null;
      try
      {
        obj = xmlResource.getEObject((String) ref.getValue());
      }
      catch (RuntimeException exception)
      {
        cause = exception;
      }

      if (obj != null)
      {
        // We won't need to process this again later.
        if (!isEndDocument)
        {
          i.remove();
        }
        EStructuralFeature feature = ref.getFeature();
        setFeatureValue(ref.getObject(), feature, obj, ref.getPosition());
      }
      else if (isEndDocument)
      {
        UnresolvedReferenceException exception = new UnresolvedReferenceException(ref.getObject(), ref.getFeature(), (String) ref.getValue(), getLocation(), ref.getLineNumber(), ref.getColumnNumber());
        if (cause != null)
        {
          exception.initCause(cause);
        }
        error(exception);
      }
    }

    for (Iterator<ManyReference> i = forwardManyReferences.iterator(); i.hasNext(); )
    {
      ManyReference ref =  i.next();
      Object[] values = ref.getValues();

      boolean failure = false;
      for (int j = 0, l = values.length; j < l; j++)
      {
        String id = (String) values[j];
        EObject obj = null;
        RuntimeException cause = null;
        
        try
        {
          obj = xmlResource.getEObject(id);
          values[j] = obj;
        }
        catch (RuntimeException exception)
        {
          cause = exception;
        }

        if (obj == null)
        {
          failure = true;
          if (isEndDocument)
          {
            UnresolvedReferenceException exception = new UnresolvedReferenceException(ref.getObject(), ref.getFeature(), id, getLocation(), ref.getLineNumber(), ref.getColumnNumber()); 
            if (cause != null)
            {
              exception.initCause(cause);
            }
            error(exception);
          }
        }
      }

      if (!failure)
      {
        if (!isEndDocument)
        {
          i.remove();
        }
        setFeatureValues(ref);
      }
      else if (isEndDocument)
      {
        // At least set the references that we were able to resolve, if any.
        //
        setFeatureValues(ref);
      }
    }
  }

  /**
   * Check if the values of the forward references have been set (they may
   * have been set due to a bi-directional reference being set).  If not,
   * set them.
   */
  @Override
  public void endDocument()
  {
    if (deferredExtent != null)
    {
      extent.addAll(deferredExtent);
    }

    // Pretend there is an xmlns="" because we really need to ensure that the null prefix
    // isn't used to denote something other than the null namespace.
    //
    if (usedNullNamespacePackage)
    {
      helper.addPrefix("", "");
    }
    helper.recordPrefixToURIMapping();
    helper.popContext();
    handleForwardReferences(true);

    if (disableNotify)
    {
      for (Iterator<?> i = EcoreUtil.getAllContents(xmlResource.getContents(), false); i.hasNext(); )
      {
        EObject eObject = (EObject)i.next();
        eObject.eSetDeliver(true);
      }
    }

    if (extendedMetaData != null)
    {
      if (extent.size() == 1)
      {
        EObject root = extent.get(0);
        recordNamespacesSchemaLocations(root);
      }

      if (DEBUG_DEMANDED_PACKAGES)
      {
        // EATM temporary for debug purposes only.
        //
        Collection<EPackage> demandedPackages = EcoreUtil.copyAll(extendedMetaData.demandedPackages());
        for (EPackage ePackage : demandedPackages)
        {
          ePackage.setName(ePackage.getNsURI());
        }
        extent.addAll(demandedPackages);
      }
    }
  }

  protected EMap<String, String> recordNamespacesSchemaLocations(EObject root)
  {
    EClass eClass = root.eClass();
    EReference xmlnsPrefixMapFeature = extendedMetaData.getXMLNSPrefixMapFeature(eClass);
    EMap<String, String> xmlnsPrefixMap = null;
    if (xmlnsPrefixMapFeature != null)
    {
      @SuppressWarnings("unchecked") EMap<String, String> newXMLNSPrefixMap = (EMap<String, String>)root.eGet(xmlnsPrefixMapFeature);
      xmlnsPrefixMap = newXMLNSPrefixMap;
      xmlnsPrefixMap.putAll(helper.getPrefixToNamespaceMap());
    }

    if (urisToLocations != null)
    {
      EReference xsiSchemaLocationMapFeature = extendedMetaData.getXSISchemaLocationMapFeature(eClass);
      if (xsiSchemaLocationMapFeature != null)
      {
        @SuppressWarnings("unchecked") EMap<String, String> newXSISchemaLocationMap = (EMap<String, String>)root.eGet(xsiSchemaLocationMapFeature);
        EMap<String, String> xsiSchemaLocationMap = newXSISchemaLocationMap;
        for (Map.Entry<String, URI> entry : urisToLocations.entrySet())
        {
          xsiSchemaLocationMap.put(entry.getKey(), entry.getValue().toString());
        }
      }
    }
    return xmlnsPrefixMap;
  }

  /**
   * Create an object based on the prefix and type name.
   */
  protected EObject createObjectByType(String prefix, String name, boolean top)
  {
    if (top)
    {
      handleTopLocations(prefix, name);
    }

    EFactory eFactory = getFactoryForPrefix(prefix);
    String uri =  helper.getURI(prefix);
    if (eFactory == null && prefix.equals("") && uri == null)
    {
      EPackage ePackage = handleMissingPackage(null);
      if (ePackage == null)
      {
        error
          (new PackageNotFoundException
             (null,
              getLocation(),
              getLineNumber(),
              getColumnNumber()));
      }
      else
      {
        eFactory = ePackage.getEFactoryInstance();
      }
    }

    documentRoot= createDocumentRoot(prefix, uri, name, eFactory, top);

    if (documentRoot != null) return documentRoot;

    EObject newObject = null;
    if (useNewMethods)
    {
      newObject = createObject(eFactory, helper.getType(eFactory, name) , false);
    }
    else
    {
      newObject = createObjectFromFactory(eFactory, name);
    }
    newObject = validateCreateObjectFromFactory(eFactory, name, newObject, top);

    if (top)
    {
      processTopObject(newObject);
      // check for simple feature
      if (extendedMetaData != null && newObject != null)
      {
        EStructuralFeature simpleFeature = extendedMetaData.getSimpleFeature(newObject.eClass());
        if (simpleFeature != null)
        {
          isSimpleFeature = true;
          isIDREF = simpleFeature instanceof EReference;
          objects.push(null);
          mixedTargets.push(null);
          types.push(simpleFeature);
          text = new StringBuffer();
        }
      }
    }
    return newObject;
  }

  protected EObject createDocumentRoot(String prefix, String uri, String name, EFactory eFactory, boolean top)
  {
    if (extendedMetaData != null && eFactory != null)
    {
      EPackage ePackage = eFactory.getEPackage();
      EClass eClass = null;
      if (useConfigurationCache)
      {
        eClass = ConfigurationCache.INSTANCE.getDocumentRoot(ePackage);
        if (eClass == null)
        {
          eClass = extendedMetaData.getDocumentRoot(ePackage);
          ConfigurationCache.INSTANCE.putDocumentRoot(ePackage, eClass);
        }
      }
      else
      {
        eClass = extendedMetaData.getDocumentRoot(ePackage);
      }
      if (eClass != null)
      {
        // EATM Kind of hacky.
        String typeName = extendedMetaData.getName(eClass);
        @SuppressWarnings("deprecation") EObject newObject =
          useNewMethods ?
            createObject(eFactory, eClass, true) :
              helper.createObject(eFactory, typeName);
        validateCreateObjectFromFactory(eFactory, typeName, newObject);
        if (top)
        {
          if (suppressDocumentRoot)
          {
            // Set up a deferred extent so the document root we create definitely will not be added to the resource.
            //
            List<EObject> oldDeferredExtent = deferredExtent;
            try
            {
              deferredExtent = new ArrayList<EObject>();
              processTopObject(newObject);
            }
            finally
            {
              deferredExtent = oldDeferredExtent;
            }
            handleFeature(prefix, name);

            // Remove the document root's information from the top of the stacks.
            //
            objects.remove(0);
            mixedTargets.remove(0);
            types.remove(0);

            // Process the new root object if any.
            //
            EObject peekObject = objects.peek();
            if (peekObject == null)
            {
              // There's an EObject on the stack already.
              //
              if (objects.size() > 1)
              {
                // Excise the new root from the document root.
                //
                EcoreUtil.remove(peekObject = objects.get(0));
              }
              else
              {
                // If there is no root object, we're dealing with an EAttribute feature instead of an EReference feature.
                // So create an instance of simple any type and prepare it to handle the text content.
                //
                SimpleAnyType simpleAnyType = (SimpleAnyType)EcoreUtil.create(anySimpleType);
                simpleAnyType.setInstanceType(((EAttribute)types.peek()).getEAttributeType());
                objects.set(0, simpleAnyType);
                types.set(0, XMLTypePackage.Literals.SIMPLE_ANY_TYPE__RAW_VALUE);
                mixedTargets.set(0, simpleAnyType.getMixed());
                peekObject = simpleAnyType;
              }
            }
            else
            {
              // Excise the new root from the document root.
              //
              EcoreUtil.remove(peekObject);
            }
            // Do the extent processing that should have been done for the root but was actualljy done for the document root.
            //
            if (deferredExtent != null)
            {
              deferredExtent.add(peekObject);
            }
            else
            {
              extent.addUnique(peekObject);
            }

            // The new root object is the actual new object since all sign of the document root will now have disappeared.
            //
            newObject = peekObject;
          }
          else
          {
            processTopObject(newObject);
            handleFeature(prefix, name);
          }
        }
        return newObject;
      }
    }
    return null;
  }

  protected void createTopObject(String prefix, String name)
  {
    createObjectByType(prefix, name, true);
  }

  /**
   * Add object to extent and call processObject.
   */
  protected void processTopObject(EObject object)
  {
    if (object != null)
    {
      if (deferredExtent != null)
      {
        deferredExtent.add(object);
      }
      else
      {
        extent.addUnique(object);
      }

      if (extendedMetaData != null && !mixedTargets.isEmpty())
      {
        FeatureMap featureMap = mixedTargets.pop();
        EStructuralFeature target = extendedMetaData.getMixedFeature(object.eClass());
        if (target != null)
        {
          FeatureMap otherFeatureMap = (FeatureMap)object.eGet(target);
          for (FeatureMap.Entry entry : new ArrayList<FeatureMap.Entry>(featureMap))
          {
            // Ignore a whitespace only text entry at the beginning.
            //
            if (entry.getEStructuralFeature() !=  XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__TEXT ||
                  !"".equals(XMLTypeUtil.normalize(entry.getValue().toString(), true)))
            {
              otherFeatureMap.add(entry.getEStructuralFeature(), entry.getValue());
            }
          }
        }
        text = null;
      }
    }

    processObject(object);
  }

  /**
   * Pop the appropriate stacks and set features whose values are in
   * the content of XML elements.
   */
  @Override
  public void endElement(String uri, String localName, String name)
  {
    elements.pop();
    Object type = types.pop();
    if (type == OBJECT_TYPE)
    {
      if (text == null)
      {
        objects.pop();
        mixedTargets.pop();
      }
      else
      {
        EObject object = objects.popEObject();
        if (mixedTargets.peek() != null &&
              (object.eContainer() != null ||
                 suppressDocumentRoot ||
                 recordUnknownFeature &&
                   (eObjectToExtensionMap.containsValue(object) || ((InternalEObject)object).eDirectResource() != null)))
        {
          handleMixedText();
          mixedTargets.pop();
        }
        else
        {
          if (text.length() != 0)
          {
            handleProxy((InternalEObject)object, text.toString().trim());
          }
          mixedTargets.pop();
          text = null;
        }
      }
    }
    else if (isIDREF)
    {
      objects.pop();
      mixedTargets.pop();
      if (text != null)
      {
        setValueFromId(objects.peekEObject(), (EReference)type, text.toString());
        text = null;
      }
      isIDREF= false;
    }
    else if (isTextFeatureValue(type))
    {
      EObject eObject = objects.popEObject();
      mixedTargets.pop();
      if (eObject == null)
      {
        eObject = objects.peekEObject();
      }
      setFeatureValue(eObject, (EStructuralFeature) type, text == null ? null : text.toString());
      text = null;
    }

    if (isSimpleFeature)
    {
      types.pop();
      objects.pop();
      mixedTargets.pop();
      isSimpleFeature = false;
    }
    helper.popContext(prefixesToFactories);
  }

  protected boolean isTextFeatureValue(Object type)
  {
    return type != ERROR_TYPE;
  }

  @Override
  public void startPrefixMapping(String prefix, String uri)
  {
    isNamespaceAware = true;

    if (needsPushContext)
    {
      helper.pushContext();
      needsPushContext = false;
    }

    //if (useNonDeprecatedMethods)
    //{
        helper.addPrefix(prefix, uri);
        prefixesToFactories.remove(prefix);
    //}
  }

  @Override
  public void endPrefixMapping(String prefix)
  {
    // Do nothing.
  }

  @Override
  public void characters(char [] ch, int start, int length)
  {
    if (text == null && mixedTargets.peek() != null)
    {
      text = new StringBuffer();
    }

    if (text != null)
    {
      text.append(ch, start, length);
    }
  }

  @Override
  public void processingInstruction(String target, String data)
  {
    if (mixedTargets.peek() != null)
    {
      if (text != null)
      {
        handleMixedText();
      }

      handleProcessingInstruction(target, data);
    }
  }

  protected void handleXMLNSAttribute(String attrib, String value)
  {
    // Handle namespaces
    int index = attrib.indexOf(':', 0);
    String prefix = index == -1 ? "" : attrib.substring(index + 1);
    helper.addPrefix(prefix, value);
    prefixesToFactories.remove(prefix);
  }

  protected void handleXSISchemaLocation(String schemaLocations)
  {
    if (urisToLocations == null)
    {
      urisToLocations = new HashMap<String, URI>();
      xmlResource.getDefaultSaveOptions().put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
    }

    for (StringTokenizer stringTokenizer = new StringTokenizer(schemaLocations, " "); stringTokenizer.hasMoreTokens(); )
    {
      String key = stringTokenizer.nextToken();
      if (stringTokenizer.hasMoreTokens())
      {
        String value = stringTokenizer.nextToken();
        URI uri = URI.createURI(value);
        if (uriHandler != null)
        {
          uri = uriHandler.resolve(uri);
        }
        else if (resolve && uri.isRelative() && uri.hasRelativePath())
        {
          uri = helper.resolve(uri, resourceURI);
        }
        urisToLocations.put(key, uri);
      }
    }
  }

  protected void handleXSINoNamespaceSchemaLocation(String noNamespaceSchemaLocation)
  {
    if (urisToLocations == null)
    {
      urisToLocations = new HashMap<String, URI>();
      xmlResource.getDefaultSaveOptions().put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
    }

    URI uri = URI.createURI(noNamespaceSchemaLocation);
    if (uriHandler != null)
    {
      uri = uriHandler.resolve(uri);
    }
    else if (resolve && uri.isRelative() && uri.hasRelativePath())
    {
      uri = helper.resolve(uri, resourceURI);
    }
    urisToLocations.put(null, uri);
  }

  protected void processSchemaLocations(String prefix, String name)
  {
    if (urisToLocations != null)
    {
      // If processSchemaLocations is specified, treat these as XML Schema locations
      if (processSchemaLocations)
      {
        try
        {
          ecoreBuilder.generate(urisToLocations);
        }
        catch (Exception exception)
        {
          XMIPlugin.INSTANCE.log(exception);
        }
      }
      // If externalSchemaLocations are specified, process these ones as well
      try
      {
        if (externalURIToLocations != null)
        {
          ecoreBuilder.generate(externalURIToLocations);
        }
      }
      catch (Exception exception)
      {
        XMIPlugin.INSTANCE.log(exception);
      }

      URI locationForNull = urisToLocations.get(null);
      if (locationForNull != null && helper.getNoNamespacePackage() == null)
      {
        helper.setNoNamespacePackage(getPackageForURI(locationForNull.toString()));
      }
    }
    else if (externalURIToLocations != null)
    {
      try
      {
        ecoreBuilder.generate(externalURIToLocations);
      }
      catch (Exception exception)
      {
        XMIPlugin.INSTANCE.log(exception);
      }
    }
  }

  protected void handleTopLocations(String prefix, String name)
  {
    processSchemaLocations(prefix, name);
    if (processAnyXML)
    {
      // Ensure that anything can be handled, even if it's not recognized.
      //
      String uri = helper.getURI(prefix);
      if (extendedMetaData.getPackage(uri) == null)
      {
        extendedMetaData.demandFeature(uri, name, true);
      }
    }
  }

  /**
   * The XML element represents a feature. There are two
   * cases to handle:
   *   1. The feature has a type that is a datatype.
   *   2. The feature has a type that is a class.
   */
  protected void handleFeature(String prefix, String name)
  {
    EObject peekObject = objects.peekEObject();

    // This happens when processing an element with simple content that has elements content even though it shouldn't.
    //
    if (peekObject == null)
    {
      types.push(ERROR_TYPE);
      error
        (new FeatureNotFoundException
          (name,
           null,
           getLocation(),
           getLineNumber(),
           getColumnNumber()));
      return;
    }

    EStructuralFeature feature = getFeature(peekObject, prefix, name, true);
    if (feature != null)
    {
      int kind = helper.getFeatureKind(feature);
      if (kind == XMLHelper.DATATYPE_SINGLE || kind == XMLHelper.DATATYPE_IS_MANY)
      {
        objects.push(null);
        mixedTargets.push(null);
        types.push(feature);
        if (!isNull())
        {
          text = new StringBuffer();
        }
      }
      else if (extendedMetaData != null)
      {
        EReference eReference = (EReference)feature;
        boolean isContainment = eReference.isContainment();
        if (!isContainment && !eReference.isResolveProxies() && extendedMetaData.getFeatureKind(feature) != ExtendedMetaData.UNSPECIFIED_FEATURE)
        {
          isIDREF = true;
          objects.push(null);
          mixedTargets.push(null);
          types.push(feature);
          text = new StringBuffer();
        }
        else
        {
          createObject(peekObject, feature);
          EObject childObject = objects.peekEObject();
          if (childObject != null)
          {
            if (isContainment)
            {
              EStructuralFeature simpleFeature = extendedMetaData.getSimpleFeature(childObject.eClass());
              if (simpleFeature != null)
              {
                isSimpleFeature = true;
                isIDREF = simpleFeature instanceof EReference;
                objects.push(null);
                mixedTargets.push(null);
                types.push(simpleFeature);
                text = new StringBuffer();
              }
            }
            else if (!childObject.eIsProxy())
            {
              text = new StringBuffer();
            }
          }
        }
      }
      else
      {
        createObject(peekObject, feature);
      }
    }
    else
    {
      // Try to get a general-content feature.
      // Use a pattern that's not possible any other way.
      //
      if (xmlMap != null && (feature = getFeature(peekObject, null, "", true)) != null)
      {

        EFactory eFactory = getFactoryForPrefix(prefix);

        // This is for the case for a local unqualified element that has been bound.
        //
        if (eFactory == null)
        {
          eFactory = feature.getEContainingClass().getEPackage().getEFactoryInstance();
        }

        EObject newObject = null;
        if (useNewMethods)
        {
          newObject = createObject(eFactory, helper.getType(eFactory, name), false);
        }
        else
        {
            newObject = createObjectFromFactory(eFactory, name);
        }
        newObject = validateCreateObjectFromFactory(eFactory, name, newObject, feature);
        if (newObject != null)
        {
          setFeatureValue(peekObject, feature, newObject);
        }
        processObject(newObject);
      }
      else
      {
        // This handles the case of a substitution group.
        //
        if (xmlMap != null)
        {
          EFactory eFactory = getFactoryForPrefix(prefix);
          EObject newObject = createObjectFromFactory(eFactory, name);
          validateCreateObjectFromFactory(eFactory, name, newObject);
          if (newObject != null)
          {
            for (EReference eReference : peekObject.eClass().getEAllReferences())
            {
              if (eReference.getEType().isInstance(newObject))
              {
                setFeatureValue(peekObject, eReference, newObject);
                processObject(newObject);
                return;
              }
            }
          }
        }

        handleUnknownFeature(prefix, name, true, peekObject, null);
      }
    }
  }

  protected int getLineNumber()
  {
    if (locator != null)
    {
      return locator.getLineNumber();
    }
    else
    {
      return -1;
    }
  }

  protected int getColumnNumber()
  {
    if (locator != null)
    {
      return locator.getColumnNumber();
    }
    else
    {
      return -1;
    }
  }

  protected String getLocation()
  {
    return
      locator != null && locator.getSystemId() != null ?
        locator.getSystemId() :
        resourceURI == null ? "" : resourceURI.toString();
  }

  protected AnyType getExtension(EObject peekObject)
  {
    AnyType anyType = eObjectToExtensionMap.get(peekObject);
    if (anyType == null)
    {
      anyType = XMLTypeFactory.eINSTANCE.createAnyType();
      eObjectToExtensionMap.put(peekObject, anyType);
    }
    return anyType;
  }

  protected void handleUnknownFeature(String prefix, String name, boolean isElement, EObject peekObject, String value)
  {
    if (recordUnknownFeature)
    {
      recordUnknownFeature(prefix, name, isElement, peekObject, value);
    }
    else
    {
      reportUnknownFeature(prefix, name, isElement, peekObject, value);
    }
  }

  protected void recordUnknownFeature(String prefix, String name, boolean isElement, EObject peekObject, String value)
  {
    if (isElement)
    {
      AnyType anyType = getExtension(peekObject);
      int objectsIndex = objects.size();
      objects.push(anyType);
      int mixedTargetsIndex = mixedTargets.size();
      mixedTargets.push(anyType.getAny());
      int typesIndex = types.size();
      types.push(UNKNOWN_FEATURE_TYPE);

      handleFeature(prefix, name);

      objects.remove(objectsIndex);
      mixedTargets.remove(mixedTargetsIndex);
      types.remove(typesIndex);
    }
    else
    {
      AnyType anyType = getExtension(peekObject);
      setAttribValue(anyType, prefix == null ? name : prefix + ":" + name, value);
    }
  }

  protected void reportUnknownFeature(String prefix, String name, boolean isElement, EObject peekObject, String value)
  {
    if (isElement)
    {
      types.push(ERROR_TYPE);
    }
    error
      (new FeatureNotFoundException
        (name,
         peekObject,
         getLocation(),
         getLineNumber(),
         getColumnNumber()));
  }

  public void error(XMIException e)
  {
    xmlResource.getErrors().add(e);
  }

  public void warning(XMIException e)
  {
    xmlResource.getWarnings().add(e);
  }

  public void fatalError(XMIException e)
  {
    xmlResource.getErrors().add(e);
  }

  /**
   * Create an object based on the given feature and attributes.
   */
  protected void createObject(EObject peekObject, EStructuralFeature feature)
  {
    if (isNull())
    {
      setFeatureValue(peekObject, feature, null);
      objects.push(null);
      mixedTargets.push(null);
      types.push(OBJECT_TYPE);
    }
    else
    {
      String xsiType = getXSIType();
      if (xsiType != null)
      {
        createObjectFromTypeName(peekObject, xsiType, feature);
      }
      else
      {
        createObjectFromFeatureType(peekObject, feature);
        // This check is redundant -- see handleFeature method (EL)
        /*if (extendedMetaData != null && !((EReference)feature).isContainment())
        {
          text = new StringBuffer();
        }*/
        if (xmlMap != null && !((EReference)feature).isContainment())
        {
          XMLResource.XMLInfo info = xmlMap.getInfo(feature);
          if (info != null && info.getXMLRepresentation() == XMLResource.XMLInfo.ELEMENT)
          {
            text = new StringBuffer();
          }
        }
      }
    }
  }

  /**
   * Create an object from the given qualified type name.
   */
  protected EObject createObjectFromTypeName(EObject peekObject, String typeQName, EStructuralFeature feature)
  {
    String typeName = null;
    String prefix = "";
    int index = typeQName.indexOf(':', 0);
    if (index > 0)
    {
      prefix = typeQName.substring(0, index);
      typeName = typeQName.substring(index + 1);
    }
    else
    {
      typeName = typeQName;
    }

    contextFeature = feature;
    EFactory eFactory = getFactoryForPrefix(prefix);
    contextFeature = null;

    if (eFactory == null && prefix.equals("") && helper.getURI(prefix) == null)
    {
      contextFeature = feature;
      EPackage ePackage = handleMissingPackage(null);
      contextFeature = null;
      if (ePackage == null)
      {
        error(new PackageNotFoundException(null, getLocation(), getLineNumber(), getColumnNumber()));
      }
      else
      {
        eFactory = ePackage.getEFactoryInstance();
      }
    }
    EObject obj = null;
    if (useNewMethods)
    {
      obj = createObject(eFactory, helper.getType(eFactory, typeName), false);
    }
    else
    {
      obj = createObjectFromFactory(eFactory, typeName);

    }
    obj = validateCreateObjectFromFactory(eFactory, typeName, obj, feature);

    if (obj != null)
    {
      if (contextFeature == null)
      {
        setFeatureValue(peekObject, feature, obj);
      }
      else
      {
        contextFeature = null;
      }
    }

    processObject(obj);

    return obj;
  }

  /**
   * Create an object based on the type of the given feature.
   */
  protected EObject createObjectFromFeatureType(EObject peekObject, EStructuralFeature feature)
  {
    String typeName = null;
    EFactory factory = null;
    EClassifier eType = null;
    EObject obj = null;

    if (feature != null && (eType = feature.getEType()) != null)
    {
      if (useNewMethods)
      {
        if (extendedMetaData != null && eType == EcorePackage.Literals.EOBJECT && extendedMetaData.getFeatureKind(feature) != ExtendedMetaData.UNSPECIFIED_FEATURE)
        {
          eType = anyType;
          typeName = extendedMetaData.getName(anyType);
          factory = anyType.getEPackage().getEFactoryInstance();
        }
        else
        {
          factory = eType.getEPackage().getEFactoryInstance();
          typeName = extendedMetaData == null ? eType.getName() : extendedMetaData.getName(eType);
        }
        obj = createObject(factory, eType, false);
      }
      else
      {

        if (extendedMetaData != null && eType == EcorePackage.Literals.EOBJECT && extendedMetaData.getFeatureKind(feature) != ExtendedMetaData.UNSPECIFIED_FEATURE)
        {
          typeName = extendedMetaData.getName(anyType);
          factory = anyType.getEPackage().getEFactoryInstance();
        }
        else
        {
          EClass eClass = (EClass)eType;
          typeName = extendedMetaData == null ? eClass.getName() : extendedMetaData.getName(eClass);
          factory = eClass.getEPackage().getEFactoryInstance();
        }
        obj = createObjectFromFactory(factory, typeName);
      }
    }

    obj = validateCreateObjectFromFactory(factory, typeName, obj, feature);

    if (obj != null)
    {
      setFeatureValue(peekObject, feature, obj);
    }

    processObject(obj);
    return obj;
  }

  /**
   * @deprecated since 2.2
   * Create an object given a content helper, a factory, and a type name,
   * and process the XML attributes.
   */
  @Deprecated
  protected EObject createObjectFromFactory(EFactory factory, String typeName)
  {
    EObject newObject = null;

    if (factory != null)
    {
      newObject = helper.createObject(factory, typeName);

      if (newObject != null)
      {
        if (disableNotify)
          newObject.eSetDeliver(false);

        handleObjectAttribs(newObject);
      }
    }

    return newObject;
  }

  protected EObject createObject(EFactory eFactory, EClassifier type, boolean documentRoot)
  {
    EObject newObject = helper.createObject(eFactory, type);
    if (newObject != null && !documentRoot)
    {
      if (disableNotify)
      {
        newObject.eSetDeliver(false);
      }
      handleObjectAttribs(newObject);
    }
    return newObject;
  }

  protected EObject validateCreateObjectFromFactory(EFactory factory, String typeName, EObject newObject, boolean top)
  {
    if (newObject == null && top && (recordUnknownFeature || processAnyXML) && factory != null && extendedMetaData != null)
    {
      String namespace = extendedMetaData.getNamespace(factory.getEPackage());
      if (namespace == null)
      {
        usedNullNamespacePackage = true;
      }
      if (useNewMethods)
      {
        EClassifier type = extendedMetaData.demandType(namespace, typeName);
        newObject = createObject(type.getEPackage().getEFactoryInstance(), type, false);
      }
      else
      {
        factory = extendedMetaData.demandType(namespace, typeName).getEPackage().getEFactoryInstance();
        newObject = createObjectFromFactory(factory, typeName);
      }
    }

    validateCreateObjectFromFactory(factory, typeName, newObject);
    return newObject;
  }

  protected void validateCreateObjectFromFactory(EFactory factory, String typeName, EObject newObject)
  {
    if (newObject == null)
    {
      error
        (new ClassNotFoundException
          (typeName,
           factory,
           getLocation(),
           getLineNumber(),
           getColumnNumber()));
    }
  }

  protected EObject validateCreateObjectFromFactory(EFactory factory, String typeName, EObject newObject, EStructuralFeature feature)
  {
    if (newObject != null)
    {
      if (extendedMetaData != null)
      {
        Collection<EPackage> demandedPackages = extendedMetaData.demandedPackages();
        if (!demandedPackages.isEmpty() && demandedPackages.contains(newObject.eClass().getEPackage()))
        {
          if (recordUnknownFeature)
          {
            EObject peekObject = objects.peekEObject();
            if (!(peekObject instanceof AnyType))
            {
              AnyType anyType = getExtension(objects.peekEObject());
              EStructuralFeature entryFeature =
                extendedMetaData.demandFeature(extendedMetaData.getNamespace(feature), extendedMetaData.getName(feature), true);
              anyType.getAny().add(entryFeature, newObject);
              contextFeature = entryFeature;
            }
            return newObject;
          }
          else
          {
            String namespace = extendedMetaData.getNamespace(feature);
            String name = extendedMetaData.getName(feature);
            EStructuralFeature wildcardFeature =
              extendedMetaData.getElementWildcardAffiliation((objects.peekEObject()).eClass(), namespace, name);
            if (wildcardFeature != null)
            {
              int processingKind = laxWildcardProcessing ? ExtendedMetaData.LAX_PROCESSING : extendedMetaData.getProcessingKind(wildcardFeature);
              switch (processingKind)
              {
                case ExtendedMetaData.LAX_PROCESSING:
                case ExtendedMetaData.SKIP_PROCESSING:
                {
                  return newObject;
                }
              }
            }
          }

          newObject = null;
        }
      }
    }
    else if (feature != null && factory != null && extendedMetaData != null)
    {
      // processing unknown feature with xsi:type (xmi:type)
      if (recordUnknownFeature || processAnyXML)
      {

        EObject result = null;
        String namespace = extendedMetaData.getNamespace(factory.getEPackage());
        if (namespace == null)
        {
          usedNullNamespacePackage = true;
        }
        if (useNewMethods)
        {
          EClassifier type = extendedMetaData.demandType(namespace, typeName);
          result = createObject(type.getEPackage().getEFactoryInstance(), type, false);
        }
        else
        {
          factory = extendedMetaData.demandType(namespace, typeName).getEPackage().getEFactoryInstance();
          result = createObjectFromFactory(factory, typeName);
        }
        EObject peekObject = objects.peekEObject();
        if (!(peekObject instanceof AnyType))
        {
          AnyType anyType = getExtension(peekObject);
          EStructuralFeature entryFeature =
            extendedMetaData.demandFeature(extendedMetaData.getNamespace(feature), extendedMetaData.getName(feature), true);
          anyType.getAny().add(entryFeature, result);
          contextFeature = entryFeature;
        }
        return result;
      }
      else
      {
        String namespace = extendedMetaData.getNamespace(feature);
        String name = extendedMetaData.getName(feature);
        EStructuralFeature wildcardFeature =
          extendedMetaData.getElementWildcardAffiliation((objects.peekEObject()).eClass(), namespace, name);
        if (wildcardFeature != null)
        {
          int processingKind = laxWildcardProcessing ? ExtendedMetaData.LAX_PROCESSING : extendedMetaData.getProcessingKind(wildcardFeature);
          switch (processingKind)
          {
            case ExtendedMetaData.LAX_PROCESSING:
            case ExtendedMetaData.SKIP_PROCESSING:
            {
              // EATM Demand create metadata; needs to depend on processing mode...
              String factoryNamespace = extendedMetaData.getNamespace(factory.getEPackage());
              if (factoryNamespace == null)
              {
                usedNullNamespacePackage = true;
              }
              if (useNewMethods)
              {
                EClassifier type = extendedMetaData.demandType(factoryNamespace, typeName);
                return createObject(type.getEPackage().getEFactoryInstance(), type, false);
              }
              else
              {
                factory = extendedMetaData.demandType(factoryNamespace, typeName).getEPackage().getEFactoryInstance();
                return createObjectFromFactory(factory, typeName);
              }
            }
          }
        }
      }
    }

    validateCreateObjectFromFactory(factory, typeName, newObject);

    return newObject;
  }

  /**
   * Add object to appropriate stacks.
   */
  protected void processObject(EObject object)
  {
    if (recordAnyTypeNSDecls && object instanceof AnyType)
    {
      FeatureMap featureMap = ((AnyType)object).getAnyAttribute();
      for (Map.Entry<String, String> entry : helper.getAnyContentPrefixToURIMapping().entrySet())
      {
        Object uri = entry.getValue();
        featureMap.add(extendedMetaData.demandFeature(ExtendedMetaData.XMLNS_URI, entry.getKey(), false), uri == null ? "" : uri);
      }
    }

    if (object != null)
    {
      objects.push(object);
      types.push(OBJECT_TYPE);

      if (extendedMetaData != null)
      {
        EStructuralFeature mixedFeature = extendedMetaData.getMixedFeature(object.eClass());
        if (mixedFeature != null)
        {
          mixedTargets.push((FeatureMap)object.eGet(mixedFeature));
        }
        else
        {
          mixedTargets.push(null);
        }
      }
      else
      {
        mixedTargets.push(null);
      }
    }
    else
    {
      types.push(ERROR_TYPE);
    }
  }

  protected EFactory getFactoryForPrefix(String prefix)
  {
    EFactory factory =  prefixesToFactories.get(prefix);
    if (factory == null)
    {
      String uri = helper.getURI(prefix);
      EPackage ePackage = getPackageForURI(uri);

      if (ePackage == null && uri == null && prefix.equals(""))
      {
        ePackage = helper.getNoNamespacePackage();
      }


      if (ePackage != null)
      {
        factory = ePackage.getEFactoryInstance();
        prefixesToFactories.put(prefix, factory);
        if (uri == null)
        {
          usedNullNamespacePackage = true;
        }
      }
    }

    return factory;
  }

  /**
   * Attempt to get the namespace for the given prefix, then return
   * ERegister.getPackage() or null.
   */
  protected EPackage getPackageForURI(String uriString)
  {
    if (uriString == null)
    {
      return null;
    }

    EPackage ePackage =
      extendedMetaData == null ?
        packageRegistry.getEPackage(uriString) :
        extendedMetaData.getPackage(uriString);

    if (ePackage != null && ePackage.eIsProxy())
    {
      ePackage = null;
    }

    if (ePackage == null)
    {
      URI uri = URI.createURI(uriString);
      if (uri.scheme() == null)
      {
        // This only works for old globally registered things.
        for (Map.Entry<String, Object> entry : packageRegistry.entrySet())
        {
          String nsURI = entry.getKey();
          if (nsURI != null &&
              nsURI.length() > uriString.length() &&
              nsURI.endsWith(uriString) &&
              nsURI.charAt(nsURI.length() - uriString.length() - 1) == '/')
          {
            oldStyleProxyURIs = true;
            return (EPackage)entry.getValue();
          }
        }
      }

      URI nsURI = uri;
      if (urisToLocations != null)
      {
        URI locationURI = urisToLocations.get(uriString);
        if (locationURI != null)
        {
          uri = locationURI;
        }
      }

      String fragment = uri.fragment();
      Resource resource = null;

      if ("java".equalsIgnoreCase(uri.scheme()) && uri.authority() != null)
      {
        try
        {
          String className = uri.authority();
          Class<?> javaClass = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
          Field field = javaClass.getField("eINSTANCE");
          resource = ((EPackage)field.get(null)).eResource();
        }
        catch (Exception exception)
        {
          // Ignore it if we can't find it since we'll fail anyway.
        }
      }

      if (resource == null && resourceSet != null)
      {
        URI trimmedURI = uri.trimFragment();
        resource = resourceSet.getResource(trimmedURI, false);
        if (resource != null)
        {
          if (!resource.isLoaded())
          {
            try
            {
              resource.load(resourceSet.getLoadOptions());
            }
            catch (IOException exception)
            {
              // Continue with a different approach.
            }
          }
        }
        else if (!XMLResource.XML_SCHEMA_URI.equals(uriString) && (usePackageNsURIAsLocation || uri != nsURI))
        {
          try
          {
            InputStream inputStream = getURIConverter().createInputStream(trimmedURI, null);
            resource = resourceSet.createResource(trimmedURI, ContentHandler.UNSPECIFIED_CONTENT_TYPE);
            if (resource == null)
            {
              inputStream.close();
            }
            else
            {
              resource.load(inputStream, resourceSet.getLoadOptions());
            }
          }
          catch (IOException exception)
          {
            // Continue with a different approach.
          }
        }
      }

      if (resource != null)
      {
        Object content = null;
        if (fragment != null)
        {
          content = resource.getEObject(fragment);
        }
        else
        {
          List<EObject> contents = resource.getContents();
          content = EcoreUtil.getObjectByType(contents, EcorePackage.Literals.EPACKAGE);
        }

        if (content instanceof EPackage)
        {
          ePackage = (EPackage)content;
          if (extendedMetaData != null)
          {
            extendedMetaData.putPackage(extendedMetaData.getNamespace(ePackage), ePackage);
          }
          else
          {
            packageRegistry.put(ePackage.getNsURI(), ePackage);
          }
        }
      }
    }

    if (ePackage == null)
    {
      ePackage = handleMissingPackage(uriString);
    }

    if (ePackage == null)
    {
      error
        (new PackageNotFoundException
           (uriString,
            getLocation(),
            getLineNumber(),
            getColumnNumber()));
    }

    return ePackage;
  }

  protected EPackage handleMissingPackage(String uriString)
  {
    if (XMLResource.XML_SCHEMA_URI.equals(uriString))
    {
      return xmlSchemaTypePackage;
    }
    else if (extendedMetaData != null)
    {
      if (recordUnknownFeature)
      {
        return extendedMetaData.demandPackage(uriString);
      }
      else if (processAnyXML && objects.isEmpty())
      {
        return extendedMetaData.demandPackage(uriString);
      }
      else if (contextFeature != null)
      {
        String namespace = extendedMetaData.getNamespace(contextFeature);
        String name = extendedMetaData.getName(contextFeature);
        EStructuralFeature wildcardFeature =
          extendedMetaData.getElementWildcardAffiliation((objects.peekEObject()).eClass(), namespace, name);
        if (wildcardFeature != null)
        {
          int processingKind = laxWildcardProcessing ? ExtendedMetaData.LAX_PROCESSING : extendedMetaData.getProcessingKind(wildcardFeature);
          switch (processingKind)
          {
            case ExtendedMetaData.LAX_PROCESSING:
            case ExtendedMetaData.SKIP_PROCESSING:
            {
              return extendedMetaData.demandPackage(uriString);
            }
          }
        }
      }
    }

    return missingPackageHandler != null ? missingPackageHandler.getPackage(uriString) : null;
  }

  protected URIConverter getURIConverter()
  {
    return resourceSet != null ? resourceSet.getURIConverter() : new ExtensibleURIConverterImpl();
  }

  protected void setFeatureValue(EObject object, EStructuralFeature feature, Object value)
  {
    setFeatureValue(object, feature, value, -1);
  }

  /**
   * Set the given feature of the given object to the given value.
   */
  protected void setFeatureValue(EObject object, EStructuralFeature feature, Object value, int position)
  {
    try
    {
      helper.setValue(object, feature, value, position);
    }
    catch (RuntimeException e)
    {
      error
        (new IllegalValueException
           (object,
            feature,
            value,
            e,
            getLocation(),
            getLineNumber(),
            getColumnNumber()));
    }
  }

  /**
   * Set the values for the given multi-valued forward reference.
   */
  protected void setFeatureValues(ManyReference reference)
  {
    List<XMIException> xmiExceptions = helper.setManyReference(reference, getLocation());

    if (xmiExceptions != null)
    {
      for (XMIException exception : xmiExceptions)
      {
        error(exception);
      }
    }
  }

  /**
   * Create a feature with the given name for the given object with the
   * given values.
   */
  protected void setAttribValue(EObject object, String name, String value)
  {
    int index = name.indexOf(':', 0);

    // We use null here instead of "" because an attribute without a prefix is considered to have the null target namespace...
    String prefix = null;
    String localName = name;
    if (index != -1)
    {
      prefix    = name.substring(0, index);
      localName = name.substring(index + 1);
    }
    EStructuralFeature feature = getFeature(object, prefix, localName, false);
    if (feature == null)
    {
      handleUnknownFeature(prefix, localName, false, object, value);
    }
    else
    {
      int kind = helper.getFeatureKind(feature);

      if (kind == XMLHelper.DATATYPE_SINGLE || kind == XMLHelper.DATATYPE_IS_MANY)
      {
        setFeatureValue(object, feature, value, -2);
      }
      else
      {
        setValueFromId(object, (EReference)feature, value);
      }
    }
  }

  /**
   * Create a ValueLine object and put it in the list
   * of references to resolve at the end of the document.
   */
  protected void setValueFromId(EObject object, EReference eReference, String ids)
  {
    StringTokenizer st = new StringTokenizer(ids);

    boolean isFirstID = true;
    boolean mustAdd = deferIDREFResolution;
    boolean mustAddOrNotOppositeIsMany = false;

    int size = 0;
    String qName = null;
    int position = 0;
    while (st.hasMoreTokens())
    {
      String id = st.nextToken();
      int index = id.indexOf('#', 0);
      if (index != -1)
      {
        if (index == 0)
        {
          id = id.substring(1);
        }
        else
        {
          Object oldAttributes = setAttributes(null);
          // Create a proxy in the correct way and pop it.
          //
          InternalEObject proxy =
            (InternalEObject)
              (qName == null ?
                 createObjectFromFeatureType(object, eReference) :
                 createObjectFromTypeName(object, qName, eReference));
          setAttributes(oldAttributes);
          if (proxy != null)
          {
            handleProxy(proxy, id);
          }
          objects.pop();
          types.pop();
          mixedTargets.pop();

          qName = null;
          ++position;
          continue;
        }
      }
      else if (id.indexOf(':', 0) != -1)
      {
        qName = id;
        continue;
      }

      // Ensure that references corresponding to repeating elements are always deferred and processed in order at the end.
      //
      if (isFirstID && extendedMetaData != null && eReference.isMany() && extendedMetaData.getFeatureKind(eReference) == ExtendedMetaData.ELEMENT_FEATURE)
      {
        SingleReference ref = new SingleReference
                                   (object,
                                    eReference,
                                    id,
                                    -1,
                                    getLineNumber(),
                                    getColumnNumber());
        forwardSingleReferences.add(ref);
        return;
      }

      if (!deferIDREFResolution)
      {
        if (isFirstID)
        {
          EReference eOpposite = eReference.getEOpposite();
          if (eOpposite == null)
          {
            mustAdd = true;
            mustAddOrNotOppositeIsMany = true;
          }
          else
          {
            mustAdd = eOpposite.isTransient() || eReference.isMany();
            mustAddOrNotOppositeIsMany = mustAdd || !eOpposite.isMany();
          }
          isFirstID = false;
        }

        if (mustAddOrNotOppositeIsMany)
        {
          EObject resolvedEObject = xmlResource.getEObject(id);
          if (resolvedEObject != null)
          {
            setFeatureValue(object, eReference, resolvedEObject);
            qName = null;
            ++position;
            continue;
          }
        }
      }

      if (mustAdd)
      {
        if (size == capacity)
          growArrays();

        identifiers[size] = id;
        positions[size]   = position;
        ++size;
      }
      qName = null;
      ++position;
    }

    if (position == 0)
    {
      setFeatureValue(object, eReference, null, -2);
    }
    else if (size <= REFERENCE_THRESHOLD)
    {
      for (int i = 0; i < size; i++)
      {
        SingleReference ref = new SingleReference
                                   (object,
                                    eReference,
                                    identifiers[i],
                                    positions[i],
                                    getLineNumber(),
                                    getColumnNumber());
        forwardSingleReferences.add(ref);
      }
    }
    else
    {
      Object[] values = new Object[size];
      int[] currentPositions = new int[size];
      System.arraycopy(identifiers, 0, values, 0, size);
      System.arraycopy(positions, 0, currentPositions, 0, size);

      ManyReference ref = new ManyReference
                                 (object,
                                  eReference,
                                  values,
                                  currentPositions,
                                  getLineNumber(),
                                  getColumnNumber());
      forwardManyReferences.add(ref);
    }
  }

  protected void handleProxy(InternalEObject proxy, String uriLiteral)
  {
    URI proxyURI;
    if (oldStyleProxyURIs)
    {
      proxy.eSetProxyURI(proxyURI = URI.createURI(uriLiteral.startsWith("/") ? uriLiteral : "/" + uriLiteral));
    }
    else
    {
      URI uri = URI.createURI(uriLiteral);
      if (uriHandler != null)
      {
        uri = uriHandler.resolve(uri);
      }
      else if (resolve &&
            uri.isRelative() &&
            uri.hasRelativePath() &&
            (extendedMetaData == null ?
              !packageRegistry.containsKey(uri.trimFragment().toString()) :
              extendedMetaData.getPackage(uri.trimFragment().toString()) == null))
      {
        uri = helper.resolve(uri, resourceURI);
      }
      proxy.eSetProxyURI(proxyURI = uri);
    }

    // Test for a same document reference that would usually be handled as an IDREF.
    //
    if (proxyURI.trimFragment().equals(resourceURI))
    {
      sameDocumentProxies.add(proxy);
    }
  }

  protected void growArrays() {
    int oldCapacity = capacity;
    capacity = capacity * 2;
    Object[] newIdentifiers = new Object[capacity];
    int[] newPositions = new int[capacity];
    System.arraycopy(identifiers, 0, newIdentifiers, 0, oldCapacity);
    System.arraycopy(positions, 0, newPositions, 0, oldCapacity);
    identifiers = newIdentifiers;
    positions = newPositions;
  }

  /**
   * Returns true if there was an error in the last XML element; false otherwise.
   */
  protected boolean isError()
  {
    return types.peek() == ERROR_TYPE;
  }

  static class EClassFeatureNamePair
  {
    public EClass eClass;
    public String featureName;
    public String namespaceURI;
    public boolean isElement;

    @Override
    public boolean equals(Object that)
    {
      EClassFeatureNamePair typedThat = (EClassFeatureNamePair)that;
      return
        typedThat.eClass == eClass &&
        typedThat.isElement == isElement &&
        typedThat.featureName.equals(featureName) &&
        (typedThat.namespaceURI != null ? typedThat.namespaceURI.equals(namespaceURI): namespaceURI == null);
    }

    @Override
    public int hashCode()
    {
      return eClass.hashCode() ^ featureName.hashCode() ^ (namespaceURI == null ? 0 : namespaceURI.hashCode()) + (isElement ? 0 : 1);
    }
  }

  Map<EClassFeatureNamePair, EStructuralFeature> eClassFeatureNamePairToEStructuralFeatureMap;
  boolean isOptionUseXMLNameToFeatureSet;
  EClassFeatureNamePair eClassFeatureNamePair = new  EClassFeatureNamePair();

  /**
   * @deprecated
   */
  @Deprecated
  protected EStructuralFeature getFeature(EObject object, String prefix, String name)
  {
    EClass eClass = object.eClass();
    String uri = helper.getURI(prefix);
    EStructuralFeature result = helper.getFeature(eClass, uri, name, true);
    if (result == null)
    {
      helper.getFeature(eClass, uri, name, false);
    }
    return result;
  }

  /**
   * Get the EStructuralFeature from the metaObject for the given object
   * and feature name.
   */
  protected EStructuralFeature getFeature(EObject object, String prefix, String name, boolean isElement)
  {
    String uri = helper.getURI(prefix);
    EClass eClass = object.eClass();
    eClassFeatureNamePair.eClass = eClass;
    eClassFeatureNamePair.featureName = name;
    eClassFeatureNamePair.namespaceURI = uri;
    eClassFeatureNamePair.isElement = isElement;
    EStructuralFeature result = eClassFeatureNamePairToEStructuralFeatureMap.get(eClassFeatureNamePair);
    if (result == null)
    {
      result = helper.getFeature(eClass, uri, name, isElement);

      if (result == null)
      {
        if (extendedMetaData != null)
        {
          EStructuralFeature wildcardFeature =
            isElement ?
              extendedMetaData.getElementWildcardAffiliation(eClass, uri, name) :
              extendedMetaData.getAttributeWildcardAffiliation(eClass, uri, name);
          if (wildcardFeature != null)
          {
            int processingKind = laxWildcardProcessing ? ExtendedMetaData.LAX_PROCESSING : extendedMetaData.getProcessingKind(wildcardFeature);
            switch (processingKind)
            {
              case ExtendedMetaData.LAX_PROCESSING:
              case ExtendedMetaData.SKIP_PROCESSING:
              {
                // EATM Demand create metadata.
                result = extendedMetaData.demandFeature(uri, name, isElement);
                break;
              }
            }
          }
        }
        else
        {
          // EATM Call the deprecated method which does the same thing
          // but might have an override in older code.
          result = getFeature(object, prefix, name);
        }
      }

      EClassFeatureNamePair entry = new EClassFeatureNamePair();
      entry.eClass = eClass;
      entry.featureName = name;
      entry.namespaceURI = uri;
      entry.isElement = isElement;
      eClassFeatureNamePairToEStructuralFeatureMap.put(entry, result);
    }

    return result;
  }

  /**
   * Searches the array of bytes to determine the XML
   * encoding.
   */
  public static String getXMLEncoding(byte[] bytes)
  {
    String javaEncoding = null;

    if (bytes.length >= 4)
    {
      if (((bytes[0] == -2) && (bytes[1] == -1))  ||
          ((bytes[0] == 0) && (bytes[1] == 60)))
        javaEncoding = "UnicodeBig";
      else if (((bytes[0] == -1) && (bytes[1] == -2)) ||
                ((bytes[0] == 60) && (bytes[1] == 0)))
        javaEncoding = "UnicodeLittle";
      else if ((bytes[0] == -17) && (bytes[1] == -69) && (bytes[2] == -65))
        javaEncoding = "UTF8";
    }

    String header = null;

    try
    {
      if (javaEncoding != null)
        header = new String(bytes, 0, bytes.length, javaEncoding);
      else
        header = new String(bytes, 0, bytes.length);
    }
    catch (UnsupportedEncodingException e)
    {
      return null;
    }

    if (!header.startsWith("<?xml"))
      return "UTF-8";

    int endOfXMLPI = header.indexOf("?>");
    int encodingIndex = header.indexOf("encoding", 6);

    if ((encodingIndex == -1) || (encodingIndex > endOfXMLPI))
      return "UTF-8";

    int firstQuoteIndex = header.indexOf('"', encodingIndex);
    int lastQuoteIndex;

    if ((firstQuoteIndex == -1) || (firstQuoteIndex > endOfXMLPI))
    {
      firstQuoteIndex = header.indexOf('\'', encodingIndex);
      lastQuoteIndex = header.indexOf('\'', firstQuoteIndex + 1);
    }
    else
      lastQuoteIndex = header.indexOf('"', firstQuoteIndex + 1);

    return header.substring(firstQuoteIndex + 1, lastQuoteIndex);
  }

  protected void handleComment(String comment)
  {
    FeatureMap featureMap = mixedTargets.peek();
    featureMap.add(XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__COMMENT, comment);
    text = null;
  }
  protected void handleMixedText()
  {
    FeatureMap featureMap = mixedTargets.peek();
    featureMap.add(XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__TEXT, text.toString());
    text = null;
  }

  protected void handleCDATA()
  {
    FeatureMap featureMap = mixedTargets.peek();
    featureMap.add(XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__CDATA, text.toString());
    text = null;
  }

  protected void handleProcessingInstruction(String target, String data)
  {
    FeatureMap featureMap = mixedTargets.peek();
    FeatureMapUtil.addProcessingInstruction(featureMap, target, data);
    text = null;
  }

  protected EcoreBuilder createEcoreBuilder(Map<?, ?> options, ExtendedMetaData extendedMetaData)
  {
    return new DefaultEcoreBuilder(extendedMetaData);
  }
}
