content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.dom;
  
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.lang.reflect.Constructor;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.WeakHashMap;
  
  import mf.org.apache.xerces.util.URI;
  import mf.org.apache.xerces.util.XML11Char;
  import mf.org.apache.xerces.util.XMLChar;
  import mf.org.apache.xerces.xni.NamespaceContext;
  import mf.org.w3c.dom.Attr;
  import mf.org.w3c.dom.CDATASection;
  import mf.org.w3c.dom.Comment;
  import mf.org.w3c.dom.DOMConfiguration;
  import mf.org.w3c.dom.DOMException;
  import mf.org.w3c.dom.DOMImplementation;
  import mf.org.w3c.dom.Document;
  import mf.org.w3c.dom.DocumentFragment;
  import mf.org.w3c.dom.DocumentType;
  import mf.org.w3c.dom.Element;
  import mf.org.w3c.dom.Entity;
  import mf.org.w3c.dom.EntityReference;
  import mf.org.w3c.dom.NamedNodeMap;
  import mf.org.w3c.dom.Node;
  import mf.org.w3c.dom.NodeList;
  import mf.org.w3c.dom.Notation;
  import mf.org.w3c.dom.ProcessingInstruction;
  import mf.org.w3c.dom.Text;
  import mf.org.w3c.dom.UserDataHandler;
  import mf.org.w3c.dom.events.Event;
  import mf.org.w3c.dom.events.EventListener;
  import mf.org.w3c.dom.ls.DOMImplementationLS;
  import mf.org.w3c.dom.ls.LSSerializer;
  
  
  /**
   * The Document interface represents the entire HTML or XML document.
   * Conceptually, it is the root of the document tree, and provides the
   * primary access to the document's data.
   * <p/>
   * Since elements, text nodes, comments, processing instructions,
   * etc. cannot exist outside the context of a Document, the Document
   * interface also contains the factory methods needed to create these
   * objects. The Node objects created have a ownerDocument attribute
   * which associates them with the Document within whose context they
   * were created.
   * <p/>
   * The CoreDocumentImpl class only implements the DOM Core. Additional modules
   * are supported by the more complete DocumentImpl subclass.
   * <p/>
   * <b>Note:</b> When any node in the document is serialized, the
   * entire document is serialized along with it.
   *
   * @author Arnaud  Le Hors, IBM
   * @author Joe Kesselman, IBM
   * @author Andy Clark, IBM
   * @author Ralf Pfeiffer, IBM
   * @version $Id: CoreDocumentImpl.java 950601 2010-06-02 15:38:55Z mrglavas $
   * @xerces.internal
   * @since PR-DOM-Level-1-19980818.
   */
  
  
  public class CoreDocumentImpl
          extends ParentNode implements Document {
  
      /**TODO::
       * 1. Change XML11Char method names similar to XMLChar. That will prevent lot
       * of dirty version checking code.
       *
       * 2. IMO during cloneNode qname/isXMLName check should not be made.
       */
      //
      // Constants
      //
  
      /**
       * Serialization version.
       */
      static final long serialVersionUID = 0;
  
      //
      // Data
      //
  
      // document information
      /**
       * Table for quick check of child insertion.
       */
      private final static int[] kidOK;
  
      static {
  
          kidOK = new int[13];
  
          kidOK[DOCUMENT_NODE] =
                  1 << ELEMENT_NODE | 1 << PROCESSING_INSTRUCTION_NODE |
                          1 << COMMENT_NODE | 1 << DOCUMENT_TYPE_NODE;
  
          kidOK[DOCUMENT_FRAGMENT_NODE] =
                  kidOK[ENTITY_NODE] =
                          kidOK[ENTITY_REFERENCE_NODE] =
                                  kidOK[ELEMENT_NODE] =
                                          1 << ELEMENT_NODE | 1 << PROCESSING_INSTRUCTION_NODE |
                                                  1 << COMMENT_NODE | 1 << TEXT_NODE |
                                                  1 << CDATA_SECTION_NODE | 1 << ENTITY_REFERENCE_NODE;
  
  
          kidOK[ATTRIBUTE_NODE] =
                  1 << TEXT_NODE | 1 << ENTITY_REFERENCE_NODE;
  
          kidOK[DOCUMENT_TYPE_NODE] =
                  kidOK[PROCESSING_INSTRUCTION_NODE] =
                          kidOK[COMMENT_NODE] =
                                  kidOK[TEXT_NODE] =
                                          kidOK[CDATA_SECTION_NODE] =
                                                  kidOK[NOTATION_NODE] =
                                                          0;
  
      } // static
  
      /**
       * Document type.
       */
      protected DocumentTypeImpl docType;
      /**
       * Document element.
       */
      protected ElementImpl docElement;
      /**
       * Experimental DOM Level 3 feature: Document encoding
       */
      protected String encoding;
      /**
       * Experimental DOM Level 3 feature: Document actualEncoding
       */
      protected String actualEncoding;
      /**
       * Experimental DOM Level 3 feature: Document version
       */
      protected String version;
      /**
       * Experimental DOM Level 3 feature: Document standalone
       */
      protected boolean standalone;
      /**
       * Experimental DOM Level 3 feature: documentURI
       */
      protected String fDocumentURI;
      /**
       * Table for user data attached to this document nodes.
       */
      protected Map userData;  // serialized as Hashtable
      /**
       * Identifiers.
       */
      protected Hashtable identifiers;
      /**
       * Number of alterations made to this document since its creation.
       * Serves as a "dirty bit" so that live objects such as NodeList can
       * recognize when an alteration has been made and discard its cached
       * state information.
       * <p/>
       * Any method that alters the tree structure MUST cause or be
       * accompanied by a call to changed(), to inform it that any outstanding
       * NodeLists may have to be updated.
       * <p/>
       * (Required because NodeList is simultaneously "live" and integer-
       * indexed -- a bad decision in the DOM's design.)
       * <p/>
       * Note that changes which do not affect the tree's structure -- changing
       * the node's name, for example -- do _not_ have to call changed().
       * <p/>
       * Alternative implementation would be to use a cryptographic
       * Digest value rather than a count. This would have the advantage that
       * "harmless" changes (those producing equal() trees) would not force
       * NodeList to resynchronize. Disadvantage is that it's slightly more prone
       * to "false negatives", though that's the difference between "wildly
       * unlikely" and "absurdly unlikely". IF we start maintaining digests,
       * we should consider taking advantage of them.
       * <p/>
       * Note: This used to be done a node basis, so that we knew what
       * subtree changed. But since only DeepNodeList really use this today,
       * the gain appears to be really small compared to the cost of having
       * an int on every (parent) node plus having to walk up the tree all the
       * way to the root to mark the branch as changed everytime a node is
       * changed.
       * So we now have a single counter global to the document. It means that
       * some objects may flush their cache more often than necessary, but this
       * makes nodes smaller and only the document needs to be marked as changed.
       */
      protected int changes = 0;
      /**
       * Allow grammar access.
       */
      protected boolean allowGrammarAccess;
      /**
       * Bypass error checking.
       */
      protected boolean errorChecking = true;
      //Did version change at any point when the document was created ?
      //this field helps us to optimize when normalizingDocument.
      protected boolean xmlVersionChanged = false;
  
      // experimental
      /**
       * NodeListCache free list
       */
      transient NodeListCache fFreeNLCache;
      // DOM Level 3: normalizeDocument
      transient DOMNormalizer domNormalizer = null;
      transient DOMConfigurationImpl fConfiguration = null;
      // support of XPath API
      transient Object fXPathEvaluator = null;
      /**
       * The following are required for compareDocumentPosition
       */
      // Document number.   Documents are ordered across the implementation using
      // positive integer values.  Documents are assigned numbers on demand.
      private int documentNumber = 0;
      // Node counter and table.  Used to assign numbers to nodes for this
      // document.  Node number values are negative integers.  Nodes are
      // assigned numbers on demand.
      private int nodeCounter = 0;
      private Map nodeTable;  // serialized as Hashtable
      //
      // Static initialization
      //
      private boolean xml11Version = false; //by default 1.0
  
      //
      // Constructors
      //
  
      /**
       * NON-DOM: Actually creating a Document is outside the DOM's spec,
       * since it has to operate in terms of a particular implementation.
       */
      public CoreDocumentImpl() {
          this(false);
      }
  
      /**
       * Constructor.
       */
      public CoreDocumentImpl(boolean grammarAccess) {
          super(null);
          ownerDocument = this;
          allowGrammarAccess = grammarAccess;
      }
  
      /**
       * For DOM2 support.
       * The createDocument factory method is in DOMImplementation.
       */
      public CoreDocumentImpl(DocumentType doctype) {
          this(doctype, false);
      }
  
      /**
       * For DOM2 support.
       */
      public CoreDocumentImpl(DocumentType doctype, boolean grammarAccess) {
          this(grammarAccess);
          if (doctype != null) {
              DocumentTypeImpl doctypeImpl;
              try {
                  doctypeImpl = (DocumentTypeImpl) doctype;
              } catch (ClassCastException e) {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
                  throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
              }
              doctypeImpl.ownerDocument = this;
              appendChild(doctype);
          }
      }
  
      //
      // Node methods
      //
  
      /**
       * Check the string against XML's definition of acceptable names for
       * elements and attributes and so on using the XMLCharacterProperties
       * utility class
       */
  
      public static final boolean isXMLName(String s, boolean xml11Version) {
  
          if (s == null) {
              return false;
          }
          if (!xml11Version)
              return XMLChar.isValidName(s);
          else
              return XML11Char.isXML11ValidName(s);
  
      } // isXMLName(String):boolean
  
      /**
       * Checks if the given qualified name is legal with respect
       * to the version of XML to which this document must conform.
       *
       * @param prefix prefix of qualified name
       * @param local  local part of qualified name
       */
      public static final boolean isValidQName(String prefix, String local, boolean xml11Version) {
  
          // check that both prefix and local part match NCName
          if (local == null) return false;
          boolean validNCName = false;
  
          if (!xml11Version) {
              validNCName = (prefix == null || XMLChar.isValidNCName(prefix))
                      && XMLChar.isValidNCName(local);
          } else {
              validNCName = (prefix == null || XML11Char.isXML11ValidNCName(prefix))
                      && XML11Char.isXML11ValidNCName(local);
          }
  
          return validNCName;
      }
  
      // even though ownerDocument refers to this in this implementation
      // the DOM Level 2 spec says it must be null, so make it appear so
      @Override
      final public Document getOwnerDocument() {
          return null;
      }
  
      /**
       * Returns the node type.
       */
      @Override
      public short getNodeType() {
          return Node.DOCUMENT_NODE;
      }
  
      /**
       * Returns the node name.
       */
      @Override
      public String getNodeName() {
          return "#document";
      }
  
      /**
       * Deep-clone a document, including fixing ownerDoc for the cloned
       * children. Note that this requires bypassing the WRONG_DOCUMENT_ERR
       * protection. I've chosen to implement it by calling importNode
       * which is DOM Level 2.
       *
       * @param deep boolean, iff true replicate children
       * @return org.w3c.dom.Node
       */
      @Override
      public Node cloneNode(boolean deep) {
  
          CoreDocumentImpl newdoc = new CoreDocumentImpl();
          callUserDataHandlers(this, newdoc, UserDataHandler.NODE_CLONED);
          cloneNode(newdoc, deep);
  
          return newdoc;
  
      } // cloneNode(boolean):Node
  
      /**
       * internal method to share code with subclass
       **/
      protected void cloneNode(CoreDocumentImpl newdoc, boolean deep) {
  
          // clone the children by importing them
          if (needsSyncChildren()) {
              synchronizeChildren();
          }
  
          if (deep) {
              HashMap reversedIdentifiers = null;
              if (identifiers != null) {
                  // Build a reverse mapping from element to identifier.
                  reversedIdentifiers = new HashMap();
                  Iterator entries = identifiers.entrySet().iterator();
                  while (entries.hasNext()) {
                      Map.Entry entry = (Map.Entry) entries.next();
                      Object elementId = entry.getKey();
                      Object elementNode = entry.getValue();
                      reversedIdentifiers.put(elementNode, elementId);
                  }
              }
  
              // Copy children into new document.
              for (ChildNode kid = firstChild; kid != null;
                   kid = kid.nextSibling) {
                  newdoc.appendChild(newdoc.importNode(kid, true, true,
                          reversedIdentifiers));
              }
          }
  
          // experimental
          newdoc.allowGrammarAccess = allowGrammarAccess;
          newdoc.errorChecking = errorChecking;
  
      } // cloneNode(CoreDocumentImpl,boolean):void
  
      /**
       * Since a Document may contain at most one top-level Element child,
       * and at most one DocumentType declaraction, we need to subclass our
       * add-children methods to implement this constraint.
       * Since appendChild() is implemented as insertBefore(,null),
       * altering the latter fixes both.
       * <p/>
       * While I'm doing so, I've taken advantage of the opportunity to
       * cache documentElement and docType so we don't have to
       * search for them.
       * <p/>
       * REVISIT: According to the spec it is not allowed to alter neither the
       * document element nor the document type in any way
       */
      @Override
      public Node insertBefore(Node newChild, Node refChild)
              throws DOMException {
  
          // Only one such child permitted
          int type = newChild.getNodeType();
          if (errorChecking) {
              if (needsSyncChildren()) {
                  synchronizeChildren();
              }
              if ((type == Node.ELEMENT_NODE && docElement != null) ||
                      (type == Node.DOCUMENT_TYPE_NODE && docType != null)) {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
                  throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
              }
          }
          // Adopt orphan doctypes
          if (newChild.getOwnerDocument() == null &&
                  newChild instanceof DocumentTypeImpl) {
              ((DocumentTypeImpl) newChild).ownerDocument = this;
          }
          super.insertBefore(newChild, refChild);
  
          // If insert succeeded, cache the kid appropriately
          if (type == Node.ELEMENT_NODE) {
              docElement = (ElementImpl) newChild;
          } else if (type == Node.DOCUMENT_TYPE_NODE) {
              docType = (DocumentTypeImpl) newChild;
          }
  
          return newChild;
  
      } // insertBefore(Node,Node):Node
  
      /**
       * Since insertBefore caches the docElement (and, currently, docType),
       * removeChild has to know how to undo the cache
       * <p/>
       * REVISIT: According to the spec it is not allowed to alter neither the
       * document element nor the document type in any way
       */
      @Override
      public Node removeChild(Node oldChild) throws DOMException {
  
          super.removeChild(oldChild);
  
          // If remove succeeded, un-cache the kid appropriately
          int type = oldChild.getNodeType();
          if (type == Node.ELEMENT_NODE) {
              docElement = null;
          } else if (type == Node.DOCUMENT_TYPE_NODE) {
              docType = null;
          }
  
          return oldChild;
  
      }   // removeChild(Node):Node
  
      /**
       * Since we cache the docElement (and, currently, docType),
       * replaceChild has to update the cache
       * <p/>
       * REVISIT: According to the spec it is not allowed to alter neither the
       * document element nor the document type in any way
       */
      @Override
      public Node replaceChild(Node newChild, Node oldChild)
              throws DOMException {
  
          // Adopt orphan doctypes
          if (newChild.getOwnerDocument() == null &&
                  newChild instanceof DocumentTypeImpl) {
              ((DocumentTypeImpl) newChild).ownerDocument = this;
          }
  
          if (errorChecking && ((docType != null &&
                  oldChild.getNodeType() != Node.DOCUMENT_TYPE_NODE &&
                  newChild.getNodeType() == Node.DOCUMENT_TYPE_NODE)
                  || (docElement != null &&
                  oldChild.getNodeType() != Node.ELEMENT_NODE &&
                  newChild.getNodeType() == Node.ELEMENT_NODE))) {
  
              throw new DOMException(
                      DOMException.HIERARCHY_REQUEST_ERR,
                      DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
          }
          super.replaceChild(newChild, oldChild);
  
          int type = oldChild.getNodeType();
          if (type == Node.ELEMENT_NODE) {
              docElement = (ElementImpl) newChild;
          } else if (type == Node.DOCUMENT_TYPE_NODE) {
              docType = (DocumentTypeImpl) newChild;
          }
          return oldChild;
      }   // replaceChild(Node,Node):Node
  
      /*
       * Get Node text content
       * @since DOM Level 3
       */
      @Override
      public String getTextContent() throws DOMException {
          return null;
      }
  
      //
      // Document methods
      //
  
      // factory methods
  
      /*
       * Set Node text content
       * @since DOM Level 3
       */
      @Override
      public void setTextContent(String textContent)
              throws DOMException {
          // no-op
      }
  
      /**
       * @since DOM Level 3
       */
      @Override
      public Object getFeature(String feature, String version) {
  
          boolean anyVersion = version == null || version.length() == 0;
  
          // if a plus sign "+" is prepended to any feature name, implementations
          // are considered in which the specified feature may not be directly
          // castable DOMImplementation.getFeature(feature, version). Without a
          // plus, only features whose interfaces are directly castable are
          // considered.
          if ((feature.equalsIgnoreCase("+XPath"))
                  && (anyVersion || version.equals("3.0"))) {
  
              // If an XPathEvaluator was created previously
              // return it otherwise create a new one.
              if (fXPathEvaluator != null) {
                  return fXPathEvaluator;
              }
  
              try {
                  Class xpathClass = ObjectFactory.findProviderClass(
                          "org.apache.xpath.domapi.XPathEvaluatorImpl",
                          ObjectFactory.findClassLoader(), true);
                  Constructor xpathClassConstr =
                          xpathClass.getConstructor(Document.class);
  
                  // Check if the DOM XPath implementation implements
                  // the interface org.w3c.dom.XPathEvaluator
                  Class interfaces[] = xpathClass.getInterfaces();
                  for (int i = 0; i < interfaces.length; i++) {
                      if (interfaces[i].getName().equals(
                              "org.w3c.dom.xpath.XPathEvaluator")) {
                          fXPathEvaluator = xpathClassConstr.newInstance(this);
                          return fXPathEvaluator;
                      }
                  }
                  return null;
              } catch (Exception e) {
                  return null;
              }
          }
          return super.getFeature(feature, version);
      }
  
      /**
       * Factory method; creates an Attribute having this Document as its
       * OwnerDoc.
       *
       * @param name The name of the attribute. Note that the attribute's value is
       *             _not_ established at the factory; remember to set it!
       * @throws DOMException(INVALID_NAME_ERR) if the attribute name is not acceptable.
       */
      @Override
      public Attr createAttribute(String name)
              throws DOMException {
  
          if (errorChecking && !isXMLName(name, xml11Version)) {
              String msg =
                      DOMMessageFormatter.formatMessage(
                              DOMMessageFormatter.DOM_DOMAIN,
                              "INVALID_CHARACTER_ERR",
                              null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new AttrImpl(this, name);
  
      } // createAttribute(String):Attr
  
      /**
       * Factory method; creates a CDATASection having this Document as
       * its OwnerDoc.
       *
       * @param data The initial contents of the CDATA
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents. (HTML
       *                                         not yet implemented.)
       */
      @Override
      public CDATASection createCDATASection(String data)
              throws DOMException {
          return new CDATASectionImpl(this, data);
      }
  
      /**
       * Factory method; creates a Comment having this Document as its
       * OwnerDoc.
       *
       * @param data The initial contents of the Comment.
       */
      @Override
      public Comment createComment(String data) {
          return new CommentImpl(this, data);
      }
  
      /**
       * Factory method; creates a DocumentFragment having this Document
       * as its OwnerDoc.
       */
      @Override
      public DocumentFragment createDocumentFragment() {
          return new DocumentFragmentImpl(this);
      }
  
      /**
       * Factory method; creates an Element having this Document
       * as its OwnerDoc.
       *
       * @param tagName The name of the element type to instantiate. For
       *                XML, this is case-sensitive. For HTML, the tagName parameter may
       *                be provided in any case, but it must be mapped to the canonical
       *                uppercase form by the DOM implementation.
       * @throws DOMException(INVALID_NAME_ERR) if the tag name is not
       *                                        acceptable.
       */
      @Override
      public Element createElement(String tagName)
              throws DOMException {
  
          if (errorChecking && !isXMLName(tagName, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new ElementImpl(this, tagName);
  
      } // createElement(String):Element
  
      /**
       * Factory method; creates an EntityReference having this Document
       * as its OwnerDoc.
       *
       * @param name The name of the Entity we wish to refer to
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where
       *                                         nonstandard entities are not permitted. (HTML not yet
       *                                         implemented.)
       */
      @Override
      public EntityReference createEntityReference(String name)
              throws DOMException {
  
          if (errorChecking && !isXMLName(name, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new EntityReferenceImpl(this, name);
  
      } // createEntityReference(String):EntityReference
  
      // other document methods
  
      /**
       * Factory method; creates a ProcessingInstruction having this Document
       * as its OwnerDoc.
       *
       * @param target The target "processor channel"
       * @param data   Parameter string to be passed to the target.
       * @throws DOMException(INVALID_NAME_ERR)  if the target name is not
       *                                         acceptable.
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents. (HTML
       *                                         not yet implemented.)
       */
      @Override
      public ProcessingInstruction createProcessingInstruction(String target,
                                                               String data)
              throws DOMException {
  
          if (errorChecking && !isXMLName(target, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new ProcessingInstructionImpl(this, target, data);
  
      } // createProcessingInstruction(String,String):ProcessingInstruction
  
      /**
       * Factory method; creates a Text node having this Document as its
       * OwnerDoc.
       *
       * @param data The initial contents of the Text.
       */
      @Override
      public Text createTextNode(String data) {
          return new TextImpl(this, data);
      }
  
      /**
       * For XML, this provides access to the Document Type Definition.
       * For HTML documents, and XML documents which don't specify a DTD,
       * it will be null.
       */
      @Override
      public DocumentType getDoctype() {
          if (needsSyncChildren()) {
              synchronizeChildren();
          }
          return docType;
      }
  
      /**
       * Convenience method, allowing direct access to the child node
       * which is considered the root of the actual document content. For
       * HTML, where it is legal to have more than one Element at the top
       * level of the document, we pick the one with the tagName
       * "HTML". For XML there should be only one top-level
       * <p/>
       * (HTML not yet supported.)
       */
      @Override
      public Element getDocumentElement() {
          if (needsSyncChildren()) {
              synchronizeChildren();
          }
          return docElement;
      }
  
      //
      // Public methods
      //
  
      // properties
  
      /**
       * Return a <em>live</em> collection of all descendent Elements (not just
       * immediate children) having the specified tag name.
       *
       * @param tagname The type of Element we want to gather. "*" will be
       *                taken as a wildcard, meaning "all elements in the document."
       * @see DeepNodeListImpl
       */
      @Override
      public NodeList getElementsByTagName(String tagname) {
          return new DeepNodeListImpl(this, tagname);
      }
  
      /**
       * Retrieve information describing the abilities of this particular
       * DOM implementation. Intended to support applications that may be
       * using DOMs retrieved from several different sources, potentially
       * with different underlying representations.
       */
      @Override
      public DOMImplementation getImplementation() {
          // Currently implemented as a singleton, since it's hardcoded
          // information anyway.
          return CoreDOMImplementationImpl.getDOMImplementation();
      }
  
      /**
       * Returns true if the DOM implementation performs error checking.
       */
      public boolean getErrorChecking() {
          return errorChecking;
      }
  
      /**
       * Sets whether the DOM implementation performs error checking
       * upon operations. Turning off error checking only affects
       * the following DOM checks:
       * <ul>
       * <li>Checking strings to make sure that all characters are
       * legal XML characters
       * <li>Hierarchy checking such as allowed children, checks for
       * cycles, etc.
       * </ul>
       * <p/>
       * Turning off error checking does <em>not</em> turn off the
       * following checks:
       * <ul>
       * <li>Read only checks
       * <li>Checks related to DOM events
       * </ul>
       */
  
      public void setErrorChecking(boolean check) {
          errorChecking = check;
      }
  
      /*
       * DOM Level 3 WD - Experimental.
       */
      @Override
      public boolean getStrictErrorChecking() {
          return errorChecking;
      }
  
      /*
       * DOM Level 3 WD - Experimental.
       */
      @Override
      public void setStrictErrorChecking(boolean check) {
          errorChecking = check;
      }
  
      /**
       * DOM Level 3 CR - Experimental. (Was getActualEncoding)
       * <p/>
       * An attribute specifying the encoding used for this document
       * at the time of the parsing. This is <code>null</code> when
       * it is not known, such as when the <code>Document</code> was
       * created in memory.
       *
       * @since DOM Level 3
       */
      @Override
      public String getInputEncoding() {
          return actualEncoding;
      }
  
      /**
       * DOM Internal
       * (Was a DOM L3 Core WD public interface method setActualEncoding )
       * <p/>
       * An attribute specifying the actual encoding of this document. This is
       * <code>null</code> otherwise.
       * <br> This attribute represents the property [character encoding scheme]
       * defined in .
       */
      public void setInputEncoding(String value) {
          actualEncoding = value;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * The encoding of this document (part of XML Declaration)
       */
      @Override
      public String getXmlEncoding() {
          return encoding;
      }
  
      /**
       * DOM Internal
       * (Was a DOM L3 Core WD public interface method setXMLEncoding )
       * <p/>
       * An attribute specifying, as part of the XML declaration,
       * the encoding of this document. This is null when unspecified.
       */
      public void setXmlEncoding(String value) {
          encoding = value;
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public String getEncoding() {
          return getXmlEncoding();
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public void setEncoding(String value) {
          setXmlEncoding(value);
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * The version of this document (part of XML Declaration)
       */
      @Override
      public String getXmlVersion() {
          return (version == null) ? "1.0" : version;
      }
  
      /**
       * DOM Level 3 CR - Experimental.
       * version - An attribute specifying, as part of the XML declaration,
       * the version number of this document.
       */
      @Override
      public void setXmlVersion(String value) {
          if (value.equals("1.0") || value.equals("1.1")) {
              //we need to change the flag value only --
              // when the version set is different than already set.
              if (!getXmlVersion().equals(value)) {
                  xmlVersionChanged = true;
                  //change the normalization value back to false
                  isNormalized(false);
                  version = value;
              }
          } else {
              //NOT_SUPPORTED_ERR: Raised if the vesion is set to a value that is not supported by
              //this document
              //we dont support any other XML version
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
              throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
  
          }
          xml11Version = (getXmlVersion()).equals("1.1");
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public String getVersion() {
          return getXmlVersion();
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public void setVersion(String value) {
          setXmlVersion(value);
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * standalone that specifies whether this document is standalone
       * (part of XML Declaration)
       */
      @Override
      public boolean getXmlStandalone() {
          return standalone;
      }
  
      /**
       * DOM Level 3 CR - Experimental.
       * <p/>
       * Xmlstandalone - An attribute specifying, as part of the XML declaration,
       * whether this document is standalone
       *
       * @throws DOMException NOT_SUPPORTED_ERR: Raised if this document does not support the
       *                      "XML" feature.
       * @since DOM Level 3
       */
      @Override
      public void setXmlStandalone(boolean value)
              throws DOMException {
          standalone = value;
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public boolean getStandalone() {
          return getXmlStandalone();
      }
  
      /**
       * @deprecated This method is internal and only exists for
       * compatibility with older applications. New applications
       * should never call this method.
       */
      @Deprecated
      public void setStandalone(boolean value) {
          setXmlStandalone(value);
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * The location of the document or <code>null</code> if undefined.
       * <br>Beware that when the <code>Document</code> supports the feature
       * "HTML" , the href attribute of the HTML BASE element takes precedence
       * over this attribute.
       *
       * @since DOM Level 3
       */
      @Override
      public String getDocumentURI() {
          return fDocumentURI;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       */
      @Override
      public void setDocumentURI(String documentURI) {
          fDocumentURI = documentURI;
      }
  
      /* NON-DOM
       * Used by DOM Level 3 WD remameNode.
       *
       * Some DOM implementations do not allow nodes to be renamed and require
       * creating new elements.
       * In this case this method should be overwritten.
       *
       * @return true if the given element can be renamed, false, if it must be replaced.
       */
      protected boolean canRenameElements(String newNamespaceURI, String newNodeName, ElementImpl el) {
          return true;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * Renaming node
       */
      @Override
      public Node renameNode(Node n, String namespaceURI, String name)
              throws DOMException {
  
          if (errorChecking && n.getOwnerDocument() != this && n != this) {
              String msg = DOMMessageFormatter.formatMessage(
                      DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
              throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
          }
          switch (n.getNodeType()) {
              case ELEMENT_NODE: {
                  ElementImpl el = (ElementImpl) n;
                  if (el instanceof ElementNSImpl) {
                      if (canRenameElements(namespaceURI, name, el)) {
                          ((ElementNSImpl) el).rename(namespaceURI, name);
                          // fire user data NODE_RENAMED event
                          callUserDataHandlers(el, null, UserDataHandler.NODE_RENAMED);
                      } else {
                          el = replaceRenameElement(el, namespaceURI, name);
                      }
                  } else {
                      if (namespaceURI == null && canRenameElements(null, name, el)) {
                          el.rename(name);
                          // fire user data NODE_RENAMED event
                          callUserDataHandlers(el, null, UserDataHandler.NODE_RENAMED);
                      } else {
                          el = replaceRenameElement(el, namespaceURI, name);
                      }
                  }
                  // fire ElementNameChanged event
                  renamedElement((Element) n, el);
                  return el;
              }
              case ATTRIBUTE_NODE: {
                  AttrImpl at = (AttrImpl) n;
  
                  // detach attr from element
                  Element el = at.getOwnerElement();
                  if (el != null) {
                      el.removeAttributeNode(at);
                  }
                  if (n instanceof AttrNSImpl) {
                      ((AttrNSImpl) at).rename(namespaceURI, name);
                      // reattach attr to element
                      if (el != null) {
                          el.setAttributeNodeNS(at);
                      }
  
                      // fire user data NODE_RENAMED event
                      callUserDataHandlers(at, null, UserDataHandler.NODE_RENAMED);
                  } else {
                      if (namespaceURI == null) {
                          at.rename(name);
                          // reattach attr to element
                          if (el != null) {
                              el.setAttributeNode(at);
                          }
  
                          // fire user data NODE_RENAMED event
                          callUserDataHandlers(at, null, UserDataHandler.NODE_RENAMED);
                      } else {
                          // we need to create a new object
                          AttrNSImpl nat = (AttrNSImpl) createAttributeNS(namespaceURI, name);
  
                          // register event listeners on new node
                          copyEventListeners(at, nat);
  
                          // remove user data from old node
                          Hashtable data = removeUserDataTable(at);
  
                          // move children to new node
                          Node child = at.getFirstChild();
                          while (child != null) {
                              at.removeChild(child);
                              nat.appendChild(child);
                              child = at.getFirstChild();
                          }
  
                          // attach user data to new node
                          setUserDataTable(nat, data);
  
                          // and fire user data NODE_RENAMED event
                          callUserDataHandlers(at, nat, UserDataHandler.NODE_RENAMED);
  
                          // reattach attr to element
                          if (el != null) {
                              el.setAttributeNode(nat);
                          }
                          at = nat;
                      }
                  }
                  // fire AttributeNameChanged event
                  renamedAttrNode((Attr) n, at);
  
                  return at;
              }
              default: {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
                  throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
              }
          }
  
      }
  
      private ElementImpl replaceRenameElement(ElementImpl el, String namespaceURI, String name) {
  
          // we need to create a new object
          ElementNSImpl nel = (ElementNSImpl) createElementNS(namespaceURI, name);
  
          // register event listeners on new node
          copyEventListeners(el, nel);
  
          // remove user data from old node
          Hashtable data = removeUserDataTable(el);
  
          // remove old node from parent if any
          Node parent = el.getParentNode();
          Node nextSib = el.getNextSibling();
          if (parent != null) {
              parent.removeChild(el);
          }
          // move children to new node
          Node child = el.getFirstChild();
          while (child != null) {
              el.removeChild(child);
              nel.appendChild(child);
              child = el.getFirstChild();
          }
          // move specified attributes to new node
          nel.moveSpecifiedAttributes(el);
  
          // attach user data to new node
          setUserDataTable(nel, data);
  
          // and fire user data NODE_RENAMED event
          callUserDataHandlers(el, nel,
                  UserDataHandler.NODE_RENAMED);
  
          // insert new node where old one was
          if (parent != null) {
              parent.insertBefore(nel, nextSib);
          }
          return nel;
      }
  
      /**
       * DOM Level 3 WD - Experimental
       * Normalize document.
       */
      @Override
      public void normalizeDocument() {
          // No need to normalize if already normalized.
          if (isNormalized() && !isNormalizeDocRequired()) {
              return;
          }
          if (needsSyncChildren()) {
              synchronizeChildren();
          }
  
          if (domNormalizer == null) {
              domNormalizer = new DOMNormalizer();
          }
  
          if (fConfiguration == null) {
              fConfiguration = new DOMConfigurationImpl();
          } else {
              fConfiguration.reset();
          }
  
          domNormalizer.normalizeDocument(this, fConfiguration);
          isNormalized(true);
          //set the XMLversion changed value to false -- once we have finished
          //doing normalization
          xmlVersionChanged = false;
      }
  
  
      //
      // DOM L3 LS
      //
  
      /**
       * DOM Level 3 CR - Experimental
       * <p/>
       * The configuration used when <code>Document.normalizeDocument</code> is
       * invoked.
       *
       * @since DOM Level 3
       */
      @Override
      public DOMConfiguration getDomConfig() {
          if (fConfiguration == null) {
              fConfiguration = new DOMConfigurationImpl();
          }
          return fConfiguration;
      }
  
      /**
       * Returns the absolute base URI of this node or null if the implementation
       * wasn't able to obtain an absolute URI. Note: If the URI is malformed, a
       * null is returned.
       *
       * @return The absolute base URI of this node or null.
       * @since DOM Level 3
       */
      @Override
      public String getBaseURI() {
          if (fDocumentURI != null && fDocumentURI.length() != 0) {// attribute value is always empty string
              try {
                  return new URI(fDocumentURI).toString();
              } catch (mf.org.apache.xerces.util.URI.MalformedURIException e) {
                  // REVISIT: what should happen in this case?
                  return null;
              }
          }
          return fDocumentURI;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * Indicates whether the method load should be synchronous or
       * asynchronous. When the async attribute is set to <code>true</code>
       * the load method returns control to the caller before the document has
       * completed loading. The default value of this property is
       * <code>false</code>.
       * <br>Setting the value of this attribute might throw NOT_SUPPORTED_ERR
       * if the implementation doesn't support the mode the attribute is being
       * set to. Should the DOM spec define the default value of this
       * property? What if implementing both async and sync IO is impractical
       * in some systems?  2001-09-14. default is <code>false</code> but we
       * need to check with Mozilla and IE.
       */
      public boolean getAsync() {
          return false;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * Indicates whether the method load should be synchronous or
       * asynchronous. When the async attribute is set to <code>true</code>
       * the load method returns control to the caller before the document has
       * completed loading. The default value of this property is
       * <code>false</code>.
       * <br>Setting the value of this attribute might throw NOT_SUPPORTED_ERR
       * if the implementation doesn't support the mode the attribute is being
       * set to. Should the DOM spec define the default value of this
       * property? What if implementing both async and sync IO is impractical
       * in some systems?  2001-09-14. default is <code>false</code> but we
       * need to check with Mozilla and IE.
       */
      public void setAsync(boolean async) {
          if (async) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
              throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
          }
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * If the document is currently being loaded as a result of the method
       * <code>load</code> being invoked the loading and parsing is
       * immediately aborted. The possibly partial result of parsing the
       * document is discarded and the document is cleared.
       */
      public void abort() {
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * <p/>
       * Replaces the content of the document with the result of parsing the
       * given URI. Invoking this method will either block the caller or
       * return to the caller immediately depending on the value of the async
       * attribute. Once the document is fully loaded a "load" event (as
       * defined in [<a href='http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331'>DOM Level 3 Events</a>]
       * , except that the <code>Event.targetNode</code> will be the document,
       * not an element) will be dispatched on the document. If an error
       * occurs, an implementation dependent "error" event will be dispatched
       * on the document. If this method is called on a document that is
       * currently loading, the current load is interrupted and the new URI
       * load is initiated.
       * <br> When invoking this method the parameters used in the
       * <code>DOMParser</code> interface are assumed to have their default
       * values with the exception that the parameters <code>"entities"</code>
       * , <code>"normalize-characters"</code>,
       * <code>"check-character-normalization"</code> are set to
       * <code>"false"</code>.
       * <br> The result of a call to this method is the same the result of a
       * call to <code>DOMParser.parseWithContext</code> with an input stream
       * referencing the URI that was passed to this call, the document as the
       * context node, and the action <code>ACTION_REPLACE_CHILDREN</code>.
       *
       * @param uri The URI reference for the XML file to be loaded. If this is
       *            a relative URI, the base URI used by the implementation is
       *            implementation dependent.
       * @return If async is set to <code>true</code> <code>load</code> returns
       * <code>true</code> if the document load was successfully initiated.
       * If an error occurred when initiating the document load,
       * <code>load</code> returns <code>false</code>.If async is set to
       * <code>false</code> <code>load</code> returns <code>true</code> if
       * the document was successfully loaded and parsed. If an error
       * occurred when either loading or parsing the URI, <code>load</code>
       * returns <code>false</code>.
       */
      public boolean load(String uri) {
          return false;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * Replace the content of the document with the result of parsing the
       * input string, this method is always synchronous.
       *
       * @param source A string containing an XML document.
       * @return <code>true</code> if parsing the input string succeeded
       * without errors, otherwise <code>false</code>.
       */
      public boolean loadXML(String source) {
          return false;
      }
  
      /**
       * DOM Level 3 WD - Experimental.
       * Save the document or the given node and all its descendants to a string
       * (i.e. serialize the document or node).
       * <br>The parameters used in the <code>LSSerializer</code> interface are
       * assumed to have their default values when invoking this method.
       * <br> The result of a call to this method is the same the result of a
       * call to <code>LSSerializer.writeToString</code> with the document as
       * the node to write.
       *
       * @param node Specifies what to serialize, if this parameter is
       *             <code>null</code> the whole document is serialized, if it's
       *             non-null the given node is serialized.
       * @return The serialized document or <code>null</code> in case an error
       * occurred.
       * @throws DOMException WRONG_DOCUMENT_ERR: Raised if the node passed in as the node
       *                      parameter is from an other document.
       */
      public String saveXML(Node node)
              throws DOMException {
          if (errorChecking && node != null &&
                  this != node.getOwnerDocument()) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
              throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
          }
          DOMImplementationLS domImplLS = (DOMImplementationLS) DOMImplementationImpl.getDOMImplementation();
          LSSerializer xmlWriter = domImplLS.createLSSerializer();
          if (node == null) {
              node = this;
          }
          return xmlWriter.writeToString(node);
      }
  
  
      // non-DOM factory methods
  
      /**
       * Returns true if the DOM implementation generates mutation events.
       */
      boolean getMutationEvents() {
          // does nothing by default - overriden in subclass
          return false;
      }
  
      /**
       * Sets whether the DOM implementation generates mutation events
       * upon operations.
       */
      void setMutationEvents(boolean set) {
          // does nothing by default - overidden in subclass
      }
  
      /**
       * NON-DOM
       * Factory method; creates a DocumentType having this Document
       * as its OwnerDoc. (REC-DOM-Level-1-19981001 left the process of building
       * DTD information unspecified.)
       *
       * @param qualifiedName
       * @param publicID
       * @param systemID
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where
       *                                         DTDs are not permitted. (HTML not yet implemented.)
       */
      public DocumentType createDocumentType(String qualifiedName,
                                             String publicID,
                                             String systemID)
              throws DOMException {
  
          return new DocumentTypeImpl(this, qualifiedName, publicID, systemID);
  
      } // createDocumentType(String):DocumentType
  
      /**
       * NON-DOM
       * Factory method; creates an Entity having this Document
       * as its OwnerDoc. (REC-DOM-Level-1-19981001 left the process of building
       * DTD information unspecified.)
       *
       * @param name The name of the Entity we wish to provide a value for.
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where
       *                                         nonstandard entities are not permitted. (HTML not yet
       *                                         implemented.)
       */
      public Entity createEntity(String name)
              throws DOMException {
  
  
          if (errorChecking && !isXMLName(name, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new EntityImpl(this, name);
  
      } // createEntity(String):Entity
  
      // other non-DOM methods
  
      /**
       * NON-DOM
       * Factory method; creates a Notation having this Document
       * as its OwnerDoc. (REC-DOM-Level-1-19981001 left the process of building
       * DTD information unspecified.)
       *
       * @param name The name of the Notation we wish to describe
       * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where
       *                                         notations are not permitted. (HTML not yet
       *                                         implemented.)
       */
      public Notation createNotation(String name)
              throws DOMException {
  
          if (errorChecking && !isXMLName(name, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new NotationImpl(this, name);
  
      } // createNotation(String):Notation
  
      /**
       * NON-DOM Factory method: creates an element definition. Element
       * definitions hold default attribute values.
       */
      public ElementDefinitionImpl createElementDefinition(String name)
              throws DOMException {
  
          if (errorChecking && !isXMLName(name, xml11Version)) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
          return new ElementDefinitionImpl(this, name);
  
      } // createElementDefinition(String):ElementDefinitionImpl
  
      /**
       * NON-DOM:  Get the number associated with this document.   Used to
       * order documents in the implementation.
       */
      @Override
      protected int getNodeNumber() {
          if (documentNumber == 0) {
  
              CoreDOMImplementationImpl cd = (CoreDOMImplementationImpl) CoreDOMImplementationImpl.getDOMImplementation();
              documentNumber = cd.assignDocumentNumber();
          }
          return documentNumber;
      }
  
      /**
       * NON-DOM:  Get a number associated with a node created with respect
       * to this document.   Needed for compareDocumentPosition when nodes
       * are disconnected.  This is only used on demand.
       */
      protected int getNodeNumber(Node node) {
  
          // Check if the node is already in the hash
          // If so, retrieve the node number
          // If not, assign a number to the node
          // Node numbers are negative, from -1 to -n
          int num;
          if (nodeTable == null) {
              nodeTable = new WeakHashMap();
              num = --nodeCounter;
              nodeTable.put(node, new Integer(num));
          } else {
              Integer n = (Integer) nodeTable.get(node);
              if (n == null) {
                  num = --nodeCounter;
                  nodeTable.put(node, new Integer(num));
              } else
                  num = n.intValue();
          }
          return num;
      }
  
      /**
       * Copies a node from another document to this document. The new nodes are
       * created using this document's factory methods and are populated with the
       * data from the source's accessor methods defined by the DOM interfaces.
       * Its behavior is otherwise similar to that of cloneNode.
       * <p/>
       * According to the DOM specifications, document nodes cannot be imported
       * and a NOT_SUPPORTED_ERR exception is thrown if attempted.
       */
      @Override
      public Node importNode(Node source, boolean deep)
              throws DOMException {
          return importNode(source, deep, false, null);
      } // importNode(Node,boolean):Node
  
      /**
       * Overloaded implementation of DOM's importNode method. This method
       * provides the core functionality for the public importNode and cloneNode
       * methods.
       * <p/>
       * The reversedIdentifiers parameter is provided for cloneNode to
       * preserve the document's identifiers. The HashMap has Elements as the
       * keys and their identifiers as the values. When an element is being
       * imported, a check is done for an associated identifier. If one exists,
       * the identifier is registered with the new, imported element. If
       * reversedIdentifiers is null, the parameter is not applied.
       */
      private Node importNode(Node source, boolean deep, boolean cloningDoc,
                              HashMap reversedIdentifiers)
              throws DOMException {
          Node newnode = null;
          Hashtable userData = null;
  
          // Sigh. This doesn't work; too many nodes have private data that
          // would have to be manually tweaked. May be able to add local
          // shortcuts to each nodetype. Consider ?????
          // if(source instanceof NodeImpl &&
          //  !(source instanceof DocumentImpl))
          // {
          //  // Can't clone DocumentImpl since it invokes us...
          //  newnode=(NodeImpl)source.cloneNode(false);
          //  newnode.ownerDocument=this;
          // }
          // else
          if (source instanceof NodeImpl)
              userData = ((NodeImpl) source).getUserDataRecord();
          int type = source.getNodeType();
          switch (type) {
              case ELEMENT_NODE: {
                  Element newElement;
                  boolean domLevel20 = source.getOwnerDocument().getImplementation().hasFeature("XML", "2.0");
                  // Create element according to namespace support/qualification.
                  if (domLevel20 == false || source.getLocalName() == null)
                      newElement = createElement(source.getNodeName());
                  else
                      newElement = createElementNS(source.getNamespaceURI(),
                              source.getNodeName());
  
                  // Copy element's attributes, if any.
                  NamedNodeMap sourceAttrs = source.getAttributes();
                  if (sourceAttrs != null) {
                      int length = sourceAttrs.getLength();
                      for (int index = 0; index < length; index++) {
                          Attr attr = (Attr) sourceAttrs.item(index);
  
                          // NOTE: this methods is used for both importingNode
                          // and cloning the document node. In case of the
                          // clonning default attributes should be copied.
                          // But for importNode defaults should be ignored.
                          if (attr.getSpecified() || cloningDoc) {
                              Attr newAttr = (Attr) importNode(attr, true, cloningDoc,
                                      reversedIdentifiers);
  
                              // Attach attribute according to namespace
                              // support/qualification.
                              if (domLevel20 == false ||
                                      attr.getLocalName() == null)
                                  newElement.setAttributeNode(newAttr);
                              else
                                  newElement.setAttributeNodeNS(newAttr);
                          }
                      }
                  }
  
                  // Register element identifier.
                  if (reversedIdentifiers != null) {
                      // Does element have an associated identifier?
                      Object elementId = reversedIdentifiers.get(source);
                      if (elementId != null) {
                          if (identifiers == null)
                              identifiers = new Hashtable();
  
                          identifiers.put(elementId, newElement);
                      }
                  }
  
                  newnode = newElement;
                  break;
              }
  
              case ATTRIBUTE_NODE: {
  
                  if (source.getOwnerDocument().getImplementation().hasFeature("XML", "2.0")) {
                      if (source.getLocalName() == null) {
                          newnode = createAttribute(source.getNodeName());
                      } else {
                          newnode = createAttributeNS(source.getNamespaceURI(),
                                  source.getNodeName());
                      }
                  } else {
                      newnode = createAttribute(source.getNodeName());
                  }
                  // if source is an AttrImpl from this very same implementation
                  // avoid creating the child nodes if possible
                  if (source instanceof AttrImpl) {
                      AttrImpl attr = (AttrImpl) source;
                      if (attr.hasStringValue()) {
                          AttrImpl newattr = (AttrImpl) newnode;
                          newattr.setValue(attr.getValue());
                          deep = false;
                      } else {
                          deep = true;
                      }
                  } else {
                      // According to the DOM spec the kids carry the value.
                      // However, there are non compliant implementations out
                      // there that fail to do so. To avoid ending up with no
                      // value at all, in this case we simply copy the text value
                      // directly.
                      if (source.getFirstChild() == null) {
                          newnode.setNodeValue(source.getNodeValue());
                          deep = false;
                      } else {
                          deep = true;
                      }
                  }
                  break;
              }
  
              case TEXT_NODE: {
                  newnode = createTextNode(source.getNodeValue());
                  break;
              }
  
              case CDATA_SECTION_NODE: {
                  newnode = createCDATASection(source.getNodeValue());
                  break;
              }
  
              case ENTITY_REFERENCE_NODE: {
                  newnode = createEntityReference(source.getNodeName());
                  // the subtree is created according to this doc by the method
                  // above, so avoid carrying over original subtree
                  deep = false;
                  break;
              }
  
              case ENTITY_NODE: {
                  Entity srcentity = (Entity) source;
                  EntityImpl newentity =
                          (EntityImpl) createEntity(source.getNodeName());
                  newentity.setPublicId(srcentity.getPublicId());
                  newentity.setSystemId(srcentity.getSystemId());
                  newentity.setNotationName(srcentity.getNotationName());
                  // Kids carry additional value,
                  // allow deep import temporarily
                  newentity.isReadOnly(false);
                  newnode = newentity;
                  break;
              }
  
              case PROCESSING_INSTRUCTION_NODE: {
                  newnode = createProcessingInstruction(source.getNodeName(),
                          source.getNodeValue());
                  break;
              }
  
              case COMMENT_NODE: {
                  newnode = createComment(source.getNodeValue());
                  break;
              }
  
              case DOCUMENT_TYPE_NODE: {
                  // unless this is used as part of cloning a Document
                  // forbid it for the sake of being compliant to the DOM spec
                  if (!cloningDoc) {
                      String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
                      throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
                  }
                  DocumentType srcdoctype = (DocumentType) source;
                  DocumentTypeImpl newdoctype = (DocumentTypeImpl)
                          createDocumentType(srcdoctype.getNodeName(),
                                  srcdoctype.getPublicId(),
                                  srcdoctype.getSystemId());
                  newdoctype.setInternalSubset(srcdoctype.getInternalSubset());
                  // Values are on NamedNodeMaps
                  NamedNodeMap smap = srcdoctype.getEntities();
                  NamedNodeMap tmap = newdoctype.getEntities();
                  if (smap != null) {
                      for (int i = 0; i < smap.getLength(); i++) {
                          tmap.setNamedItem(importNode(smap.item(i), true, true,
                                  reversedIdentifiers));
                      }
                  }
                  smap = srcdoctype.getNotations();
                  tmap = newdoctype.getNotations();
                  if (smap != null) {
                      for (int i = 0; i < smap.getLength(); i++) {
                          tmap.setNamedItem(importNode(smap.item(i), true, true,
                                  reversedIdentifiers));
                      }
                  }
  
                  // NOTE: At this time, the DOM definition of DocumentType
                  // doesn't cover Elements and their Attributes. domimpl's
                  // extentions in that area will not be preserved, even if
                  // copying from domimpl to domimpl. We could special-case
                  // that here. Arguably we should. Consider. ?????
                  newnode = newdoctype;
                  break;
              }
  
              case DOCUMENT_FRAGMENT_NODE: {
                  newnode = createDocumentFragment();
                  // No name, kids carry value
                  break;
              }
  
              case NOTATION_NODE: {
                  Notation srcnotation = (Notation) source;
                  NotationImpl newnotation =
                          (NotationImpl) createNotation(source.getNodeName());
                  newnotation.setPublicId(srcnotation.getPublicId());
                  newnotation.setSystemId(srcnotation.getSystemId());
                  // Kids carry additional value
                  newnode = newnotation;
                  // No name, no value
                  break;
              }
              case DOCUMENT_NODE: // Can't import document nodes
              default: {           // Unknown node type
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
                  throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
              }
          }
  
          if (userData != null)
              callUserDataHandlers(source, newnode, UserDataHandler.NODE_IMPORTED, userData);
  
          // If deep, replicate and attach the kids.
          if (deep) {
              for (Node srckid = source.getFirstChild();
                   srckid != null;
                   srckid = srckid.getNextSibling()) {
                  newnode.appendChild(importNode(srckid, true, cloningDoc,
                          reversedIdentifiers));
              }
          }
          if (newnode.getNodeType() == Node.ENTITY_NODE) {
              ((NodeImpl) newnode).setReadOnly(true, true);
          }
          return newnode;
  
      } // importNode(Node,boolean,boolean,Hashtable):Node
  
      // identifier maintenence
  
      /**
       * DOM Level 3 WD - Experimental
       * Change the node's ownerDocument, and its subtree, to this Document
       *
       * @param source The node to adopt.
       * @see #importNode
       **/
      @Override
      public Node adoptNode(Node source) {
          NodeImpl node;
          Hashtable userData = null;
          try {
              node = (NodeImpl) source;
          } catch (ClassCastException e) {
              // source node comes from a different DOMImplementation
              return null;
          }
  
          // Return null if the source is null
  
          if (source == null) {
              return null;
          } else if (source != null && source.getOwnerDocument() != null) {
  
              DOMImplementation thisImpl = this.getImplementation();
              DOMImplementation otherImpl = source.getOwnerDocument().getImplementation();
  
              // when the source node comes from a different implementation.
              if (thisImpl != otherImpl) {
                  // Adopting from a deferred DOM to a non-deferred DOM
                  if (thisImpl instanceof mf.org.apache.xerces.dom.DOMImplementationImpl &&
                          otherImpl instanceof mf.org.apache.xerces.dom.DeferredDOMImplementationImpl) {
                      // traverse the DOM and expand deferred nodes and then allow adoption
                      undeferChildren(node);
                  } else if (thisImpl instanceof mf.org.apache.xerces.dom.DeferredDOMImplementationImpl
                          && otherImpl instanceof mf.org.apache.xerces.dom.DOMImplementationImpl) {
                      // Adopting from a non-deferred DOM into a deferred DOM, this should be okay
                  } else {
                      // Adopting between two dissimilar DOMs is not allowed
                      return null;
                  }
              }
              // Adopting from a deferred DOM into another deferred DOM
              else if (otherImpl instanceof mf.org.apache.xerces.dom.DeferredDOMImplementationImpl) {
                  // traverse the DOM and expand deferred nodes and then allow adoption
                  undeferChildren(node);
              }
          }
  
          switch (node.getNodeType()) {
              case ATTRIBUTE_NODE: {
                  AttrImpl attr = (AttrImpl) node;
                  // remove node from wherever it is
                  if (attr.getOwnerElement() != null) {
                      //1. owner element attribute is set to null
                      attr.getOwnerElement().removeAttributeNode(attr);
                  }
                  //2. specified flag is set to true
                  attr.isSpecified(true);
                  userData = node.getUserDataRecord();
  
                  //3. change ownership
                  attr.setOwnerDocument(this);
                  if (userData != null)
                      setUserDataTable(node, userData);
                  break;
              }
              //entity, notation nodes are read only nodes.. so they can't be adopted.
              //runtime will fall through to NOTATION_NODE
              case ENTITY_NODE:
              case NOTATION_NODE: {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
                  throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  
              }
              //document, documentype nodes can't be adopted.
              //runtime will fall through to DocumentTypeNode
              case DOCUMENT_NODE:
              case DOCUMENT_TYPE_NODE: {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
                  throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
              }
              case ENTITY_REFERENCE_NODE: {
                  userData = node.getUserDataRecord();
                  // remove node from wherever it is
                  Node parent = node.getParentNode();
                  if (parent != null) {
                      parent.removeChild(source);
                  }
                  // discard its replacement value
                  Node child;
                  while ((child = node.getFirstChild()) != null) {
                      node.removeChild(child);
                  }
                  // change ownership
                  node.setOwnerDocument(this);
                  if (userData != null)
                      setUserDataTable(node, userData);
                  // set its new replacement value if any
                  if (docType == null) {
                      break;
                  }
                  NamedNodeMap entities = docType.getEntities();
                  Node entityNode = entities.getNamedItem(node.getNodeName());
                  if (entityNode == null) {
                      break;
                  }
                  for (child = entityNode.getFirstChild();
                       child != null; child = child.getNextSibling()) {
                      Node childClone = child.cloneNode(true);
                      node.appendChild(childClone);
                  }
                  break;
              }
              case ELEMENT_NODE: {
                  userData = node.getUserDataRecord();
                  // remove node from wherever it is
                  Node parent = node.getParentNode();
                  if (parent != null) {
                      parent.removeChild(source);
                  }
                  // change ownership
                  node.setOwnerDocument(this);
                  if (userData != null)
                      setUserDataTable(node, userData);
                  // reconcile default attributes
                  ((ElementImpl) node).reconcileDefaultAttributes();
                  break;
              }
              default: {
                  userData = node.getUserDataRecord();
                  // remove node from wherever it is
                  Node parent = node.getParentNode();
                  if (parent != null) {
                      parent.removeChild(source);
                  }
                  // change ownership
                  node.setOwnerDocument(this);
                  if (userData != null)
                      setUserDataTable(node, userData);
              }
          }
  
          //DOM L3 Core CR
          //http://www.w3.org/TR/2003/CR-DOM-Level-3-Core-20031107/core.html#UserDataHandler-ADOPTED
          if (userData != null)
              callUserDataHandlers(source, null, UserDataHandler.NODE_ADOPTED, userData);
  
          return node;
      }
  
      /**
       * Traverses the DOM Tree and expands deferred nodes and their
       * children.
       */
      protected void undeferChildren(Node node) {
  
          Node top = node;
  
          while (null != node) {
  
              if (((NodeImpl) node).needsSyncData()) {
                  ((NodeImpl) node).synchronizeData();
              }
  
              NamedNodeMap attributes = node.getAttributes();
              if (attributes != null) {
                  int length = attributes.getLength();
                  for (int i = 0; i < length; ++i) {
                      undeferChildren(attributes.item(i));
                  }
              }
  
              Node nextNode = null;
              nextNode = node.getFirstChild();
  
              while (null == nextNode) {
  
                  if (top.equals(node))
                      break;
  
                  nextNode = node.getNextSibling();
  
                  if (null == nextNode) {
                      node = node.getParentNode();
  
                      if ((null == node) || (top.equals(node))) {
                          nextNode = null;
                          break;
                      }
                  }
              }
  
              node = nextNode;
          }
      }
  
      /**
       * Introduced in DOM Level 2
       * Returns the Element whose ID is given by elementId. If no such element
       * exists, returns null. Behavior is not defined if more than one element
       * has this ID.
       * <p/>
       * Note: The DOM implementation must have information that says which
       * attributes are of type ID. Attributes with the name "ID" are not of type
       * ID unless so defined. Implementations that do not know whether
       * attributes are of type ID or not are expected to return null.
       *
       * @see #getIdentifier
       */
      @Override
      public Element getElementById(String elementId) {
          return getIdentifier(elementId);
      }
  
      /**
       * Remove all identifiers from the ID table
       */
      protected final void clearIdentifiers() {
          if (identifiers != null) {
              identifiers.clear();
          }
      }
  
      /**
       * Registers an identifier name with a specified element node.
       * If the identifier is already registered, the new element
       * node replaces the previous node. If the specified element
       * node is null, removeIdentifier() is called.
       *
       * @see #getIdentifier
       * @see #removeIdentifier
       */
      public void putIdentifier(String idName, Element element) {
  
          if (element == null) {
              removeIdentifier(idName);
              return;
          }
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          if (identifiers == null) {
              identifiers = new Hashtable();
          }
  
          identifiers.put(idName, element);
  
      } // putIdentifier(String,Element)
  
      /**
       * Returns a previously registered element with the specified
       * identifier name, or null if no element is registered.
       *
       * @see #putIdentifier
       * @see #removeIdentifier
       */
      public Element getIdentifier(String idName) {
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          if (identifiers == null) {
              return null;
          }
          Element elem = (Element) identifiers.get(idName);
          if (elem != null) {
              // check that the element is in the tree
              Node parent = elem.getParentNode();
              while (parent != null) {
                  if (parent == this) {
                      return elem;
                  }
                  parent = parent.getParentNode();
              }
          }
          return null;
      } // getIdentifier(String):Element
  
      //
      // DOM2: Namespace methods
      //
  
      /**
       * Removes a previously registered element with the specified
       * identifier name.
       *
       * @see #putIdentifier
       * @see #getIdentifier
       */
      public void removeIdentifier(String idName) {
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          if (identifiers == null) {
              return;
          }
  
          identifiers.remove(idName);
  
      } // removeIdentifier(String)
  
      /**
       * Returns an enumeration registered of identifier names.
       */
      public Enumeration getIdentifiers() {
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          if (identifiers == null) {
              identifiers = new Hashtable();
          }
  
          return identifiers.keys();
  
      } // getIdentifiers():Enumeration
  
      /**
       * Introduced in DOM Level 2. <p>
       * Creates an element of the given qualified name and namespace URI.
       * If the given namespaceURI is null or an empty string and the
       * qualifiedName has a prefix that is "xml", the created element
       * is bound to the predefined namespace
       * "http://www.w3.org/XML/1998/namespace" [Namespaces].
       *
       * @param namespaceURI  The namespace URI of the element to
       *                      create.
       * @param qualifiedName The qualified name of the element type to
       *                      instantiate.
       * @return Element A new Element object with the following attributes:
       * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified
       *                      name contains an invalid character.
       * @throws DOMException NAMESPACE_ERR: Raised if the qualifiedName has a
       *                      prefix that is "xml" and the namespaceURI is
       *                      neither null nor an empty string nor
       *                      "http://www.w3.org/XML/1998/namespace", or
       *                      if the qualifiedName has a prefix different
       *                      from "xml" and the namespaceURI is null or an
       *                      empty string.
       * @since WD-DOM-Level-2-19990923
       */
      @Override
      public Element createElementNS(String namespaceURI, String qualifiedName)
              throws DOMException {
          return new ElementNSImpl(this, namespaceURI, qualifiedName);
      }
  
      /**
       * NON-DOM: a factory method used by the Xerces DOM parser
       * to create an element.
       *
       * @param namespaceURI  The namespace URI of the element to
       *                      create.
       * @param qualifiedName The qualified name of the element type to
       *                      instantiate.
       * @param localpart     The local name of the attribute to instantiate.
       * @return Element A new Element object with the following attributes:
       * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified
       *                      name contains an invalid character.
       */
      public Element createElementNS(String namespaceURI, String qualifiedName,
                                     String localpart)
              throws DOMException {
          return new ElementNSImpl(this, namespaceURI, qualifiedName, localpart);
      }
  
      /**
       * Introduced in DOM Level 2. <p>
       * Creates an attribute of the given qualified name and namespace URI.
       * If the given namespaceURI is null or an empty string and the
       * qualifiedName has a prefix that is "xml", the created element
       * is bound to the predefined namespace
       * "http://www.w3.org/XML/1998/namespace" [Namespaces].
       *
       * @param namespaceURI  The namespace URI of the attribute to
       *                      create. When it is null or an empty string,
       *                      this method behaves like createAttribute.
       * @param qualifiedName The qualified name of the attribute to
       *                      instantiate.
       * @return Attr         A new Attr object.
       * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified
       *                      name contains an invalid character.
       * @since WD-DOM-Level-2-19990923
       */
      @Override
      public Attr createAttributeNS(String namespaceURI, String qualifiedName)
              throws DOMException {
          return new AttrNSImpl(this, namespaceURI, qualifiedName);
      }
  
      //
      // Object methods
      //
  
      /**
       * NON-DOM: a factory method used by the Xerces DOM parser
       * to create an element.
       *
       * @param namespaceURI  The namespace URI of the attribute to
       *                      create. When it is null or an empty string,
       *                      this method behaves like createAttribute.
       * @param qualifiedName The qualified name of the attribute to
       *                      instantiate.
       * @param localpart     The local name of the attribute to instantiate.
       * @return Attr         A new Attr object.
       * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified
       *                      name contains an invalid character.
       */
      public Attr createAttributeNS(String namespaceURI, String qualifiedName,
                                    String localpart)
              throws DOMException {
          return new AttrNSImpl(this, namespaceURI, qualifiedName, localpart);
      }
  
      //
      // Public static methods
      //
  
      /**
       * Introduced in DOM Level 2. <p>
       * Returns a NodeList of all the Elements with a given local name and
       * namespace URI in the order in which they would be encountered in a
       * preorder traversal of the Document tree.
       *
       * @param namespaceURI The namespace URI of the elements to match
       *                     on. The special value "*" matches all
       *                     namespaces. When it is null or an empty
       *                     string, this method behaves like
       *                     getElementsByTagName.
       * @param localName    The local name of the elements to match on.
       *                     The special value "*" matches all local names.
       * @return NodeList     A new NodeList object containing all the matched
       * Elements.
       * @since WD-DOM-Level-2-19990923
       */
      @Override
      public NodeList getElementsByTagNameNS(String namespaceURI,
                                             String localName) {
          return new DeepNodeListImpl(this, namespaceURI, localName);
      }
  
      /**
       * Clone.
       */
      @Override
      public Object clone() throws CloneNotSupportedException {
          CoreDocumentImpl newdoc = (CoreDocumentImpl) super.clone();
          newdoc.docType = null;
          newdoc.docElement = null;
          return newdoc;
      }
      //
      // Protected methods
      //
  
      /**
       * Uses the kidOK lookup table to check whether the proposed
       * tree structure is legal.
       */
      protected boolean isKidOK(Node parent, Node child) {
          if (allowGrammarAccess &&
                  parent.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
              return child.getNodeType() == Node.ELEMENT_NODE;
          }
          return 0 != (kidOK[parent.getNodeType()] & 1 << child.getNodeType());
      }
  
      /**
       * Denotes that this node has changed.
       */
      @Override
      protected void changed() {
          changes++;
      }
  
      /**
       * Returns the number of changes to this node.
       */
      @Override
      protected int changes() {
          return changes;
      }
  
      //  NodeListCache pool
  
      /**
       * Returns a NodeListCache for the given node.
       */
      NodeListCache getNodeListCache(ParentNode owner) {
          if (fFreeNLCache == null) {
              return new NodeListCache(owner);
          }
          NodeListCache c = fFreeNLCache;
          fFreeNLCache = fFreeNLCache.next;
          c.fChild = null;
          c.fChildIndex = -1;
          c.fLength = -1;
          // revoke previous ownership
          if (c.fOwner != null) {
              c.fOwner.fNodeListCache = null;
          }
          c.fOwner = owner;
          // c.next = null; not necessary, except for confused people...
          return c;
      }
  
      /**
       * Puts the given NodeListCache in the free list.
       * Note: The owner node can keep using it until we reuse it
       */
      void freeNodeListCache(NodeListCache c) {
          c.next = fFreeNLCache;
          fFreeNLCache = c;
      }
  
  
      /**
       * Associate an object to a key on this node. The object can later be
       * retrieved from this node by calling <code>getUserData</code> with the
       * same key.
       *
       * @param n       The node to associate the object to.
       * @param key     The key to associate the object to.
       * @param data    The object to associate to the given key, or
       *                <code>null</code> to remove any existing association to that key.
       * @param handler The handler to associate to that key, or
       *                <code>null</code>.
       * @return Returns the <code>DOMObject</code> previously associated to
       * the given key on this node, or <code>null</code> if there was none.
       * @since DOM Level 3
       * <p/>
       * REVISIT: we could use a free list of UserDataRecord here
       */
      public Object setUserData(Node n, String key,
                                Object data, UserDataHandler handler) {
          if (data == null) {
              if (userData != null) {
                  Hashtable t = (Hashtable) userData.get(n);
                  if (t != null) {
                      Object o = t.remove(key);
                      if (o != null) {
                          UserDataRecord r = (UserDataRecord) o;
                          return r.fData;
                      }
                  }
              }
              return null;
          } else {
              Hashtable t;
              if (userData == null) {
                  userData = new WeakHashMap();
                  t = new Hashtable();
                  userData.put(n, t);
              } else {
                  t = (Hashtable) userData.get(n);
                  if (t == null) {
                      t = new Hashtable();
                      userData.put(n, t);
                  }
              }
              Object o = t.put(key, new UserDataRecord(data, handler));
              if (o != null) {
                  UserDataRecord r = (UserDataRecord) o;
                  return r.fData;
              }
              return null;
          }
      }
  
  
      /**
       * Retrieves the object associated to a key on a this node. The object
       * must first have been set to this node by calling
       * <code>setUserData</code> with the same key.
       *
       * @param n   The node the object is associated to.
       * @param key The key the object is associated to.
       * @return Returns the <code>DOMObject</code> associated to the given key
       * on this node, or <code>null</code> if there was none.
       * @since DOM Level 3
       */
      public Object getUserData(Node n, String key) {
          if (userData == null) {
              return null;
          }
          Hashtable t = (Hashtable) userData.get(n);
          if (t == null) {
              return null;
          }
          Object o = t.get(key);
          if (o != null) {
              UserDataRecord r = (UserDataRecord) o;
              return r.fData;
          }
          return null;
      }
  
      protected Hashtable getUserDataRecord(Node n) {
          if (userData == null) {
              return null;
          }
          Hashtable t = (Hashtable) userData.get(n);
          if (t == null) {
              return null;
          }
          return t;
      }
  
      /**
       * Remove user data table for the given node.
       *
       * @param n The node this operation applies to.
       * @return The removed table.
       */
      Hashtable removeUserDataTable(Node n) {
          if (userData == null) {
              return null;
          }
          return (Hashtable) userData.get(n);
      }
  
      /**
       * Set user data table for the given node.
       *
       * @param n    The node this operation applies to.
       * @param data The user data table.
       */
      void setUserDataTable(Node n, Hashtable data) {
          if (userData == null) {
              userData = new WeakHashMap();
          }
          if (data != null) {
              userData.put(n, data);
          }
      }
  
      /**
       * Call user data handlers when a node is deleted (finalized)
       *
       * @param n         The node this operation applies to.
       * @param c         The copy node or null.
       * @param operation The operation - import, clone, or delete.
       */
      protected void callUserDataHandlers(Node n, Node c, short operation) {
          if (userData == null) {
              return;
          }
          //Hashtable t = (Hashtable) userData.get(n);
          if (n instanceof NodeImpl) {
              Hashtable t = ((NodeImpl) n).getUserDataRecord();
              if (t == null || t.isEmpty()) {
                  return;
              }
              callUserDataHandlers(n, c, operation, t);
          }
      }
  
      /**
       * Call user data handlers when a node is deleted (finalized)
       *
       * @param n         The node this operation applies to.
       * @param c         The copy node or null.
       * @param operation The operation - import, clone, or delete.
       * @param handlers  Data associated with n.
       */
      void callUserDataHandlers(Node n, Node c, short operation, Hashtable userData) {
          if (userData == null || userData.isEmpty()) {
              return;
          }
          Iterator entries = userData.entrySet().iterator();
          while (entries.hasNext()) {
              Map.Entry entry = (Map.Entry) entries.next();
              String key = (String) entry.getKey();
              UserDataRecord r = (UserDataRecord) entry.getValue();
              if (r.fHandler != null) {
                  r.fHandler.handle(operation, key, r.fData, n, c);
              }
          }
      }
  
      /**
       * Call user data handlers to let them know the nodes they are related to
       * are being deleted. The alternative would be to do that on Node but
       * because the nodes are used as the keys we have a reference to them that
       * prevents them from being gc'ed until the document is. At the same time,
       * doing it here has the advantage of avoiding a finalize() method on Node,
       * which would affect all nodes and not just the ones that have a user
       * data.
       */
      // Temporarily comment out this method, because
      // 1. It seems that finalizers are not guaranteed to be called, so the
      //    functionality is not implemented.
      // 2. It affects the performance greatly in multi-thread environment.
      // -SG
      /*public void finalize() {
          if (userData == null) {
              return;
          }
          Enumeration nodes = userData.keys();
          while (nodes.hasMoreElements()) {
              Object node = nodes.nextElement();
              Hashtable t = (Hashtable) userData.get(node);
              if (t != null && !t.isEmpty()) {
                  Enumeration keys = t.keys();
                  while (keys.hasMoreElements()) {
                      String key = (String) keys.nextElement();
                      UserDataRecord r = (UserDataRecord) t.get(key);
                      if (r.fHandler != null) {
                          r.fHandler.handle(UserDataHandler.NODE_DELETED,
                                            key, r.fData, null, null);
                      }
                  }
              }
          }
      }*/
      protected final void checkNamespaceWF(String qname, int colon1,
                                            int colon2) {
  
          if (!errorChecking) {
              return;
          }
          // it is an error for NCName to have more than one ':'
          // check if it is valid QName [Namespace in XML production 6]
          // :camera , nikon:camera:minolta, camera:
          if (colon1 == 0 || colon1 == qname.length() - 1 || colon2 != colon1) {
              String msg =
                      DOMMessageFormatter.formatMessage(
                              DOMMessageFormatter.DOM_DOMAIN,
                              "NAMESPACE_ERR",
                              null);
              throw new DOMException(DOMException.NAMESPACE_ERR, msg);
          }
      }
  
      protected final void checkDOMNSErr(String prefix,
                                         String namespace) {
          if (errorChecking) {
              if (namespace == null) {
                  String msg =
                          DOMMessageFormatter.formatMessage(
                                  DOMMessageFormatter.DOM_DOMAIN,
                                  "NAMESPACE_ERR",
                                  null);
                  throw new DOMException(DOMException.NAMESPACE_ERR, msg);
              } else if (prefix.equals("xml")
                      && !namespace.equals(NamespaceContext.XML_URI)) {
                  String msg =
                          DOMMessageFormatter.formatMessage(
                                  DOMMessageFormatter.DOM_DOMAIN,
                                  "NAMESPACE_ERR",
                                  null);
                  throw new DOMException(DOMException.NAMESPACE_ERR, msg);
              } else if (
                      prefix.equals("xmlns")
                              && !namespace.equals(NamespaceContext.XMLNS_URI)
                              || (!prefix.equals("xmlns")
                              && namespace.equals(NamespaceContext.XMLNS_URI))) {
                  String msg =
                          DOMMessageFormatter.formatMessage(
                                  DOMMessageFormatter.DOM_DOMAIN,
                                  "NAMESPACE_ERR",
                                  null);
                  throw new DOMException(DOMException.NAMESPACE_ERR, msg);
              }
          }
      }
  
      /**
       * Checks if the given qualified name is legal with respect
       * to the version of XML to which this document must conform.
       *
       * @param prefix prefix of qualified name
       * @param local  local part of qualified name
       */
      protected final void checkQName(String prefix, String local) {
          if (!errorChecking) {
              return;
          }
  
          // check that both prefix and local part match NCName
          boolean validNCName = false;
          if (!xml11Version) {
              validNCName = (prefix == null || XMLChar.isValidNCName(prefix))
                      && XMLChar.isValidNCName(local);
          } else {
              validNCName = (prefix == null || XML11Char.isXML11ValidNCName(prefix))
                      && XML11Char.isXML11ValidNCName(local);
          }
  
          if (!validNCName) {
              // REVISIT: add qname parameter to the message
              String msg =
                      DOMMessageFormatter.formatMessage(
                              DOMMessageFormatter.DOM_DOMAIN,
                              "INVALID_CHARACTER_ERR",
                              null);
              throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg);
          }
      }
  
      /**
       * We could have more xml versions in future , but for now we could
       * do with this to handle XML 1.0 and 1.1
       */
      boolean isXML11Version() {
          return xml11Version;
      }
  
      boolean isNormalizeDocRequired() {
          // REVISIT: Implement to optimize when normalization
          // is required
          return true;
      }
  
      //we should be checking the (elements, attribute, entity etc.) names only when
      //version of the document is changed.
      boolean isXMLVersionChanged() {
          return xmlVersionChanged;
      }
  
      /**
       * NON-DOM: kept for backward compatibility
       * Store user data related to a given node
       * This is a place where we could use weak references! Indeed, the node
       * here won't be GC'ed as long as some user data is attached to it, since
       * the userData table will have a reference to the node.
       */
      protected void setUserData(NodeImpl n, Object data) {
          setUserData(n, "XERCES1DOMUSERDATA", data, null);
      }
  
      /**
       * NON-DOM: kept for backward compatibility
       * Retreive user data related to a given node
       */
      protected Object getUserData(NodeImpl n) {
          return getUserData(n, "XERCES1DOMUSERDATA");
      }
  
  
      // Event related methods overidden in subclass
  
      protected void addEventListener(NodeImpl node, String type,
                                      EventListener listener,
                                      boolean useCapture) {
          // does nothing by default - overidden in subclass
      }
  
      protected void removeEventListener(NodeImpl node, String type,
                                         EventListener listener,
                                         boolean useCapture) {
          // does nothing by default - overidden in subclass
      }
  
      protected void copyEventListeners(NodeImpl src, NodeImpl tgt) {
          // does nothing by default - overidden in subclass
      }
  
      protected boolean dispatchEvent(NodeImpl node, Event event) {
          // does nothing by default - overidden in subclass
          return false;
      }
  
      // Notification methods overidden in subclasses
  
      /**
       * A method to be called when some text was changed in a text node,
       * so that live objects can be notified.
       */
      void replacedText(CharacterDataImpl node) {
      }
  
      /**
       * A method to be called when some text was deleted from a text node,
       * so that live objects can be notified.
       */
      void deletedText(CharacterDataImpl node, int offset, int count) {
      }
  
      /**
       * A method to be called when some text was inserted into a text node,
       * so that live objects can be notified.
       */
      void insertedText(CharacterDataImpl node, int offset, int count) {
      }
  
      /**
       * A method to be called when a character data node is about to be modified
       */
      void modifyingCharacterData(NodeImpl node, boolean replace) {
      }
  
      /**
       * A method to be called when a character data node has been modified
       */
      void modifiedCharacterData(NodeImpl node, String oldvalue, String value, boolean replace) {
      }
  
      /**
       * A method to be called when a node is about to be inserted in the tree.
       */
      void insertingNode(NodeImpl node, boolean replace) {
      }
  
      /**
       * A method to be called when a node has been inserted in the tree.
       */
      void insertedNode(NodeImpl node, NodeImpl newInternal, boolean replace) {
      }
  
      /**
       * A method to be called when a node is about to be removed from the tree.
       */
      void removingNode(NodeImpl node, NodeImpl oldChild, boolean replace) {
      }
  
      /**
       * A method to be called when a node has been removed from the tree.
       */
      void removedNode(NodeImpl node, boolean replace) {
      }
  
      /**
       * A method to be called when a node is about to be replaced in the tree.
       */
      void replacingNode(NodeImpl node) {
      }
  
      /**
       * A method to be called when a node has been replaced in the tree.
       */
      void replacedNode(NodeImpl node) {
      }
  
      /**
       * A method to be called when a character data node is about to be replaced
       */
      void replacingData(NodeImpl node) {
      }
  
      /**
       * method to be called when a character data node has been replaced.
       */
      void replacedCharacterData(NodeImpl node, String oldvalue, String value) {
      }
  
  
      /**
       * A method to be called when an attribute value has been modified
       */
      void modifiedAttrValue(AttrImpl attr, String oldvalue) {
      }
  
      /**
       * A method to be called when an attribute node has been set
       */
      void setAttrNode(AttrImpl attr, AttrImpl previous) {
      }
  
      /**
       * A method to be called when an attribute node has been removed
       */
      void removedAttrNode(AttrImpl attr, NodeImpl oldOwner, String name) {
      }
  
      /**
       * A method to be called when an attribute node has been renamed
       */
      void renamedAttrNode(Attr oldAt, Attr newAt) {
      }
  
      /**
       * A method to be called when an element has been renamed
       */
      void renamedElement(Element oldEl, Element newEl) {
      }
  
      /**
       * The serialized forms of the user data and node table
       * maps are Hashtables. Convert them into WeakHashMaps
       * on load.
       */
      private void readObject(ObjectInputStream in)
              throws IOException, ClassNotFoundException {
          in.defaultReadObject();
          if (userData != null) {
              userData = new WeakHashMap(userData);
          }
          if (nodeTable != null) {
              nodeTable = new WeakHashMap(nodeTable);
          }
      }
  
      /**
       * To allow DOM trees serialized by newer versions of Xerces
       * to be read by older versions briefly move the user data
       * and node table into Hashtables.
       */
      private void writeObject(ObjectOutputStream out) throws IOException {
          // Keep references to the original objects for restoration after serialization
          final Map oldUserData = this.userData;
          final Map oldNodeTable = this.nodeTable;
          try {
              if (oldUserData != null) {
                  this.userData = new Hashtable(oldUserData);
              }
              if (oldNodeTable != null) {
                  nodeTable = new Hashtable(oldNodeTable);
              }
              out.defaultWriteObject();
          }
          // If the write fails for some reason ensure 
          // that we restore the original objects.
          finally {
              this.userData = oldUserData;
              this.nodeTable = oldNodeTable;
          }
      }
  
  } // class CoreDocumentImpl
  