mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			2223 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			2223 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			Java
		
	
	
	
/* DomNode.java --
 | 
						|
   Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
 | 
						|
 | 
						|
This file is part of GNU Classpath.
 | 
						|
 | 
						|
GNU Classpath is free software; you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation; either version 2, or (at your option)
 | 
						|
any later version.
 | 
						|
 | 
						|
GNU Classpath is distributed in the hope that it will be useful, but
 | 
						|
WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with GNU Classpath; see the file COPYING.  If not, write to the
 | 
						|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
02110-1301 USA.
 | 
						|
 | 
						|
Linking this library statically or dynamically with other modules is
 | 
						|
making a combined work based on this library.  Thus, the terms and
 | 
						|
conditions of the GNU General Public License cover the whole
 | 
						|
combination.
 | 
						|
 | 
						|
As a special exception, the copyright holders of this library give you
 | 
						|
permission to link this library with independent modules to produce an
 | 
						|
executable, regardless of the license terms of these independent
 | 
						|
modules, and to copy and distribute the resulting executable under
 | 
						|
terms of your choice, provided that you also meet, for each linked
 | 
						|
independent module, the terms and conditions of the license of that
 | 
						|
module.  An independent module is a module which is not derived from
 | 
						|
or based on this library.  If you modify this library, you may extend
 | 
						|
this exception to your version of the library, but you are not
 | 
						|
obligated to do so.  If you do not wish to do so, delete this
 | 
						|
exception statement from your version. */
 | 
						|
 | 
						|
package gnu.xml.dom;
 | 
						|
 | 
						|
import gnu.java.lang.CPStringBuilder;
 | 
						|
 | 
						|
import java.util.HashMap;
 | 
						|
import java.util.HashSet;
 | 
						|
import java.util.Iterator;
 | 
						|
import java.util.Map;
 | 
						|
 | 
						|
import org.w3c.dom.Document;
 | 
						|
import org.w3c.dom.DOMException;
 | 
						|
import org.w3c.dom.DOMImplementation;
 | 
						|
import org.w3c.dom.NamedNodeMap;
 | 
						|
import org.w3c.dom.Node;
 | 
						|
import org.w3c.dom.NodeList;
 | 
						|
import org.w3c.dom.Text;
 | 
						|
import org.w3c.dom.UserDataHandler;
 | 
						|
import org.w3c.dom.events.DocumentEvent;
 | 
						|
import org.w3c.dom.events.Event;
 | 
						|
import org.w3c.dom.events.EventException;
 | 
						|
import org.w3c.dom.events.EventListener;
 | 
						|
import org.w3c.dom.events.EventTarget;
 | 
						|
import org.w3c.dom.events.MutationEvent;
 | 
						|
import org.w3c.dom.traversal.NodeFilter;
 | 
						|
import org.w3c.dom.traversal.NodeIterator;
 | 
						|
 | 
						|
/**
 | 
						|
 * <p> "Node", "EventTarget", and "DocumentEvent" implementation.
 | 
						|
 * This provides most of the core DOM functionality; only more
 | 
						|
 * specialized features are provided by subclasses.  Those subclasses may
 | 
						|
 * have some particular constraints they must implement, by overriding
 | 
						|
 * methods defined here.  Such constraints are noted here in the method
 | 
						|
 * documentation. </p>
 | 
						|
 *
 | 
						|
 * <p> Note that you can create events with type names prefixed with "USER-",
 | 
						|
 * and pass them through this DOM.  This lets you use the DOM event scheme
 | 
						|
 * for application specific purposes, although you must use a predefined event
 | 
						|
 * structure (such as MutationEvent) to pass data along with those events.
 | 
						|
 * Test for existence of this feature with the "USER-Events" DOM feature
 | 
						|
 * name.</p>
 | 
						|
 *
 | 
						|
 * <p> Other kinds of events you can send include the "html" events,
 | 
						|
 * like "load", "unload", "abort", "error", and "blur"; and the mutation
 | 
						|
 * events.  If this DOM has been compiled with mutation event support
 | 
						|
 * enabled, it will send mutation events when you change parts of the
 | 
						|
 * tree; otherwise you may create and send such events yourself, but
 | 
						|
 * they won't be generated by the DOM itself. </p>
 | 
						|
 *
 | 
						|
 * <p> Note that there is a namespace-aware name comparison method,
 | 
						|
 * <em>nameAndTypeEquals</em>, which compares the names (and types) of
 | 
						|
 * two nodes in conformance with the "Namespaces in XML" specification.
 | 
						|
 * While mostly intended for use with elements and attributes, this should
 | 
						|
 * also be helpful for ProcessingInstruction nodes and some others which
 | 
						|
 * do not have namespace URIs.
 | 
						|
 *
 | 
						|
 * @author David Brownell
 | 
						|
 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
 | 
						|
 */
 | 
						|
public abstract class DomNode
 | 
						|
  implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable
 | 
						|
{
 | 
						|
 | 
						|
  // package private
 | 
						|
  //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
 | 
						|
  //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
 | 
						|
 | 
						|
  // tunable
 | 
						|
  //    NKIDS_* affects arrays of children (which grow)
 | 
						|
  // (currently) fixed size:
 | 
						|
  //    ANCESTORS_* is for event capture/bubbling, # ancestors
 | 
						|
  //    NOTIFICATIONS_* is for per-node event delivery, # events
 | 
						|
  private static final int NKIDS_DELTA = 8;
 | 
						|
  private static final int ANCESTORS_INIT = 20;
 | 
						|
  private static final int NOTIFICATIONS_INIT = 10;
 | 
						|
 | 
						|
  // tunable: enable mutation events or not?  Enabling it costs about
 | 
						|
  // 10-15% in DOM construction time, last time it was measured.
 | 
						|
 | 
						|
  // package private !!!
 | 
						|
  static final boolean reportMutations = true;
 | 
						|
 | 
						|
  // locking protocol changeable only within this class
 | 
						|
  private static final Object lockNode = new Object();
 | 
						|
 | 
						|
  // NON-FINAL class data
 | 
						|
 | 
						|
  // Optimize event dispatch by not allocating memory each time
 | 
						|
  private static boolean dispatchDataLock;
 | 
						|
  private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT];
 | 
						|
  private static ListenerRecord[] notificationSet
 | 
						|
    = new ListenerRecord[NOTIFICATIONS_INIT];
 | 
						|
 | 
						|
  // Ditto for the (most common) event object itself!
 | 
						|
  private static boolean eventDataLock;
 | 
						|
  private static DomEvent.DomMutationEvent mutationEvent
 | 
						|
    = new DomEvent.DomMutationEvent(null);
 | 
						|
 | 
						|
  //
 | 
						|
  // PER-INSTANCE DATA
 | 
						|
  //
 | 
						|
 | 
						|
  DomDocument owner;
 | 
						|
  DomNode parent; // parent node;
 | 
						|
  DomNode previous; // previous sibling node
 | 
						|
  DomNode next; // next sibling node
 | 
						|
  DomNode first; // first child node
 | 
						|
  DomNode last; // last child node
 | 
						|
  int index; // index of this node in its parent's children
 | 
						|
  int depth; // depth of the node in the document
 | 
						|
  int length; // number of children
 | 
						|
  final short nodeType;
 | 
						|
 | 
						|
  // Bleech ... "package private" so a builder can populate entity refs.
 | 
						|
  // writable during construction.  DOM spec is nasty.
 | 
						|
  boolean readonly;
 | 
						|
 | 
						|
  // event registrations
 | 
						|
  private HashSet listeners;
 | 
						|
  private int nListeners;
 | 
						|
 | 
						|
  // DOM Level 3 userData dictionary.
 | 
						|
  private HashMap userData;
 | 
						|
  private HashMap userDataHandlers;
 | 
						|
 | 
						|
  //
 | 
						|
  // Some of the methods here are declared 'final' because
 | 
						|
  // knowledge about their implementation is built into this
 | 
						|
  // class -- for both integrity and performance.
 | 
						|
  //
 | 
						|
 | 
						|
  /**
 | 
						|
   * Reduces space utilization for this node.
 | 
						|
   */
 | 
						|
  public void compact()
 | 
						|
  {
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Constructs a node and associates it with its owner.  Only
 | 
						|
   * Document and DocumentType nodes may be created with no owner,
 | 
						|
   * and DocumentType nodes get an owner as soon as they are
 | 
						|
   * associated with a document.
 | 
						|
   */
 | 
						|
  protected DomNode(short nodeType, DomDocument owner)
 | 
						|
  {
 | 
						|
    this.nodeType = nodeType;
 | 
						|
 | 
						|
    if (owner == null)
 | 
						|
      {
 | 
						|
        // DOM calls never go down this path
 | 
						|
        if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE)
 | 
						|
          {
 | 
						|
            throw new IllegalArgumentException ("no owner!");
 | 
						|
          }
 | 
						|
      }
 | 
						|
    this.owner = owner;
 | 
						|
    this.listeners = new HashSet();
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns null; Element subclasses must override this method.
 | 
						|
   */
 | 
						|
  public NamedNodeMap getAttributes()
 | 
						|
  {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2></b>
 | 
						|
   * Returns true iff this is an element node with attributes.
 | 
						|
   */
 | 
						|
  public boolean hasAttributes()
 | 
						|
  {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns a list, possibly empty, of the children of this node.
 | 
						|
   * In this implementation, to conserve memory, nodes are the same
 | 
						|
   * as their list of children.  This can have ramifications for
 | 
						|
   * subclasses, which may need to provide their own getLength method
 | 
						|
   * for reasons unrelated to the NodeList method of the same name.
 | 
						|
   */
 | 
						|
  public NodeList getChildNodes()
 | 
						|
  {
 | 
						|
    return this;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns the first child of this node, or null if there are none.
 | 
						|
   */
 | 
						|
  public Node getFirstChild()
 | 
						|
  {
 | 
						|
    return first;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns the last child of this node, or null if there are none.
 | 
						|
   */
 | 
						|
  public Node getLastChild()
 | 
						|
  {
 | 
						|
    return last;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns true if this node has children.
 | 
						|
   */
 | 
						|
  public boolean hasChildNodes()
 | 
						|
  {
 | 
						|
    return length != 0;
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * Exposes the internal "readonly" flag.  In DOM, children of
 | 
						|
   * entities and entity references are readonly, as are the
 | 
						|
   * objects associated with DocumentType objets.
 | 
						|
   */
 | 
						|
  public final boolean isReadonly()
 | 
						|
  {
 | 
						|
    return readonly;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the internal "readonly" flag so this subtree can't be changed.
 | 
						|
   * Subclasses need to override this method for any associated content
 | 
						|
   * that's not a child node, such as an element's attributes or the
 | 
						|
   * (few) declarations associated with a DocumentType.
 | 
						|
   */
 | 
						|
  public void makeReadonly()
 | 
						|
  {
 | 
						|
    readonly = true;
 | 
						|
    for (DomNode child = first; child != null; child = child.next)
 | 
						|
      {
 | 
						|
        child.makeReadonly();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Used to adopt a node to a new document.
 | 
						|
   */
 | 
						|
  void setOwner(DomDocument doc)
 | 
						|
  {
 | 
						|
    this.owner = doc;
 | 
						|
    for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
      {
 | 
						|
        ctx.setOwner(doc);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // just checks the node for inclusion -- may be called many
 | 
						|
  // times (docfrag) before anything is allowed to change
 | 
						|
  private void checkMisc(DomNode child)
 | 
						|
  {
 | 
						|
    if (readonly && !owner.building)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
 | 
						|
                                  null, this, 0);
 | 
						|
      }
 | 
						|
    for (DomNode ctx = this; ctx != null; ctx = ctx.parent)
 | 
						|
      {
 | 
						|
        if (child == ctx)
 | 
						|
          {
 | 
						|
            throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
 | 
						|
                                      "can't make ancestor into a child",
 | 
						|
                                      this, 0);
 | 
						|
          }
 | 
						|
      }
 | 
						|
 | 
						|
    DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this :
 | 
						|
      this.owner;
 | 
						|
    DomDocument childOwner = child.owner;
 | 
						|
    short childNodeType = child.nodeType;
 | 
						|
 | 
						|
    if (childOwner != owner)
 | 
						|
      {
 | 
						|
        // new in DOM L2, this case -- patch it up later, in reparent()
 | 
						|
        if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null))
 | 
						|
          {
 | 
						|
            throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 | 
						|
                                      null, child, 0);
 | 
						|
          }
 | 
						|
      }
 | 
						|
 | 
						|
    // enforce various structural constraints
 | 
						|
    switch (nodeType)
 | 
						|
      {
 | 
						|
      case DOCUMENT_NODE:
 | 
						|
        switch (childNodeType)
 | 
						|
          {
 | 
						|
          case ELEMENT_NODE:
 | 
						|
          case PROCESSING_INSTRUCTION_NODE:
 | 
						|
          case COMMENT_NODE:
 | 
						|
          case DOCUMENT_TYPE_NODE:
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        break;
 | 
						|
 | 
						|
      case ATTRIBUTE_NODE:
 | 
						|
        switch (childNodeType)
 | 
						|
          {
 | 
						|
          case TEXT_NODE:
 | 
						|
          case ENTITY_REFERENCE_NODE:
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        break;
 | 
						|
 | 
						|
      case DOCUMENT_FRAGMENT_NODE:
 | 
						|
      case ENTITY_REFERENCE_NODE:
 | 
						|
      case ELEMENT_NODE:
 | 
						|
      case ENTITY_NODE:
 | 
						|
        switch (childNodeType)
 | 
						|
          {
 | 
						|
          case ELEMENT_NODE:
 | 
						|
          case TEXT_NODE:
 | 
						|
          case COMMENT_NODE:
 | 
						|
          case PROCESSING_INSTRUCTION_NODE:
 | 
						|
          case CDATA_SECTION_NODE:
 | 
						|
          case ENTITY_REFERENCE_NODE:
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        break;
 | 
						|
      case DOCUMENT_TYPE_NODE:
 | 
						|
        if (!owner.building)
 | 
						|
          break;
 | 
						|
        switch (childNodeType)
 | 
						|
          {
 | 
						|
          case COMMENT_NODE:
 | 
						|
          case PROCESSING_INSTRUCTION_NODE:
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    if (owner.checkingWellformedness)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
 | 
						|
                                  "can't append " +
 | 
						|
                                  nodeTypeToString(childNodeType) +
 | 
						|
                                  " to node of type " +
 | 
						|
                                  nodeTypeToString(nodeType),
 | 
						|
                                  this, 0);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // Here's hoping a good optimizer will detect the case when the
 | 
						|
  // next several methods are never called, and won't allocate
 | 
						|
  // object code space of any kind.  (Case:  not reporting any
 | 
						|
  // mutation events.  We can also remove some static variables
 | 
						|
  // listed above.)
 | 
						|
 | 
						|
  private void insertionEvent(DomEvent.DomMutationEvent event,
 | 
						|
                              DomNode target)
 | 
						|
  {
 | 
						|
    if (owner == null || owner.building)
 | 
						|
      {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    boolean doFree = false;
 | 
						|
 | 
						|
    if (event == null)
 | 
						|
      {
 | 
						|
        event = getMutationEvent();
 | 
						|
      }
 | 
						|
    if (event != null)
 | 
						|
      {
 | 
						|
        doFree = true;
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        event = new DomEvent.DomMutationEvent(null);
 | 
						|
      }
 | 
						|
    event.initMutationEvent("DOMNodeInserted",
 | 
						|
                            true /* bubbles */, false /* nocancel */,
 | 
						|
                            this /* related */, null, null, null, (short) 0);
 | 
						|
    target.dispatchEvent(event);
 | 
						|
 | 
						|
    // XXX should really visit every descendant of 'target'
 | 
						|
    // and sent a DOMNodeInsertedIntoDocument event to it...
 | 
						|
    // bleech, there's no way to keep that acceptably fast.
 | 
						|
 | 
						|
    if (doFree)
 | 
						|
      {
 | 
						|
        event.target = null;
 | 
						|
        event.relatedNode = null;
 | 
						|
        event.currentNode = null;
 | 
						|
        eventDataLock = false;
 | 
						|
      } // else we created work for the GC
 | 
						|
  }
 | 
						|
 | 
						|
  private void removalEvent(DomEvent.DomMutationEvent event,
 | 
						|
                            DomNode target)
 | 
						|
  {
 | 
						|
    if (owner == null || owner.building)
 | 
						|
      {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    boolean doFree = false;
 | 
						|
 | 
						|
    if (event == null)
 | 
						|
      {
 | 
						|
        event = getMutationEvent();
 | 
						|
      }
 | 
						|
    if (event != null)
 | 
						|
      {
 | 
						|
        doFree = true;
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        event = new DomEvent.DomMutationEvent(null);
 | 
						|
      }
 | 
						|
    event.initMutationEvent("DOMNodeRemoved",
 | 
						|
                            true /* bubbles */, false /* nocancel */,
 | 
						|
                            this /* related */, null, null, null, (short) 0);
 | 
						|
    target.dispatchEvent(event);
 | 
						|
 | 
						|
    // XXX should really visit every descendant of 'target'
 | 
						|
    // and sent a DOMNodeRemovedFromDocument event to it...
 | 
						|
    // bleech, there's no way to keep that acceptably fast.
 | 
						|
 | 
						|
    event.target = null;
 | 
						|
    event.relatedNode = null;
 | 
						|
    event.currentNode = null;
 | 
						|
    if (doFree)
 | 
						|
      {
 | 
						|
        eventDataLock = false;
 | 
						|
      }
 | 
						|
    // else we created more work for the GC
 | 
						|
  }
 | 
						|
 | 
						|
  //
 | 
						|
  // Avoid creating lots of memory management work, by using a simple
 | 
						|
  // allocation strategy for the mutation event objects that get used
 | 
						|
  // at least once per tree modification.  We can't use stack allocation,
 | 
						|
  // so we do the next simplest thing -- more or less, static allocation.
 | 
						|
  // Concurrent notifications should be rare, anyway.
 | 
						|
  //
 | 
						|
  // Returns the preallocated object, which needs to be carefully freed,
 | 
						|
  // or null to indicate the caller needs to allocate their own.
 | 
						|
  //
 | 
						|
  static private DomEvent.DomMutationEvent getMutationEvent()
 | 
						|
  {
 | 
						|
    synchronized (lockNode)
 | 
						|
      {
 | 
						|
        if (eventDataLock)
 | 
						|
          {
 | 
						|
            return null;
 | 
						|
          }
 | 
						|
        eventDataLock = true;
 | 
						|
        return mutationEvent;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // NOTE:  this is manually inlined in the insertion
 | 
						|
  // and removal event methods above; change in sync.
 | 
						|
  static private void freeMutationEvent()
 | 
						|
  {
 | 
						|
    // clear fields to enable GC
 | 
						|
    mutationEvent.clear();
 | 
						|
    eventDataLock = false;
 | 
						|
  }
 | 
						|
 | 
						|
  void setDepth(int depth)
 | 
						|
  {
 | 
						|
    this.depth = depth;
 | 
						|
    for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
      {
 | 
						|
        ctx.setDepth(depth + 1);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Appends the specified node to this node's list of children.
 | 
						|
   * Document subclasses must override this to enforce the restrictions
 | 
						|
   * that there be only one element and document type child.
 | 
						|
   *
 | 
						|
   * <p> Causes a DOMNodeInserted mutation event to be reported.
 | 
						|
   * Will first cause a DOMNodeRemoved event to be reported if the
 | 
						|
   * parameter already has a parent.  If the new child is a document
 | 
						|
   * fragment node, both events will be reported for each child of
 | 
						|
   * the fragment; the order in which children are removed and
 | 
						|
   * inserted is implementation-specific.
 | 
						|
   *
 | 
						|
   * <p> If this DOM has been compiled without mutation event support,
 | 
						|
   * these events will not be reported.
 | 
						|
   */
 | 
						|
  public Node appendChild(Node newChild)
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode child = (DomNode) newChild;
 | 
						|
 | 
						|
        if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
 | 
						|
          {
 | 
						|
            // Append all nodes in the fragment to this node
 | 
						|
            for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 | 
						|
              {
 | 
						|
                checkMisc(ctx);
 | 
						|
              }
 | 
						|
            for (DomNode ctx = child.first; ctx != null; )
 | 
						|
              {
 | 
						|
                DomNode ctxNext = ctx.next;
 | 
						|
                appendChild(ctx);
 | 
						|
                ctx = ctxNext;
 | 
						|
              }
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            checkMisc(child);
 | 
						|
            if (child.parent != null)
 | 
						|
              {
 | 
						|
                child.parent.removeChild(child);
 | 
						|
              }
 | 
						|
            child.parent = this;
 | 
						|
            child.index = length++;
 | 
						|
            child.setDepth(depth + 1);
 | 
						|
            child.next = null;
 | 
						|
            if (last == null)
 | 
						|
              {
 | 
						|
                first = child;
 | 
						|
                child.previous = null;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                last.next = child;
 | 
						|
                child.previous = last;
 | 
						|
              }
 | 
						|
            last = child;
 | 
						|
 | 
						|
            if (reportMutations)
 | 
						|
              {
 | 
						|
                insertionEvent(null, child);
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        return child;
 | 
						|
      }
 | 
						|
    catch (ClassCastException e)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 | 
						|
                                  null, newChild, 0);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Inserts the specified node in this node's list of children.
 | 
						|
   * Document subclasses must override this to enforce the restrictions
 | 
						|
   * that there be only one element and document type child.
 | 
						|
   *
 | 
						|
   * <p> Causes a DOMNodeInserted mutation event to be reported.  Will
 | 
						|
   * first cause a DOMNodeRemoved event to be reported if the newChild
 | 
						|
   * parameter already has a parent. If the new child is a document
 | 
						|
   * fragment node, both events will be reported for each child of
 | 
						|
   * the fragment; the order in which children are removed and inserted
 | 
						|
   * is implementation-specific.
 | 
						|
   *
 | 
						|
   * <p> If this DOM has been compiled without mutation event support,
 | 
						|
   * these events will not be reported.
 | 
						|
   */
 | 
						|
  public Node insertBefore(Node newChild, Node refChild)
 | 
						|
  {
 | 
						|
    if (refChild == null)
 | 
						|
      {
 | 
						|
        return appendChild(newChild);
 | 
						|
      }
 | 
						|
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode child = (DomNode) newChild;
 | 
						|
        DomNode ref = (DomNode) refChild;
 | 
						|
 | 
						|
        if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
 | 
						|
          {
 | 
						|
            // Append all nodes in the fragment to this node
 | 
						|
            for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 | 
						|
              {
 | 
						|
                checkMisc(ctx);
 | 
						|
              }
 | 
						|
            for (DomNode ctx = child.first; ctx != null; )
 | 
						|
              {
 | 
						|
                DomNode ctxNext = ctx.next;
 | 
						|
                insertBefore(ctx, ref);
 | 
						|
                ctx = ctxNext;
 | 
						|
              }
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            checkMisc(child);
 | 
						|
            if (ref == null || ref.parent != this)
 | 
						|
              {
 | 
						|
                throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 | 
						|
                                          null, ref, 0);
 | 
						|
              }
 | 
						|
            if (ref == child)
 | 
						|
              {
 | 
						|
                throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
 | 
						|
                                          "can't insert node before itself",
 | 
						|
                                          ref, 0);
 | 
						|
              }
 | 
						|
 | 
						|
            if (child.parent != null)
 | 
						|
              {
 | 
						|
                child.parent.removeChild(child);
 | 
						|
              }
 | 
						|
            child.parent = this;
 | 
						|
            int i = ref.index;
 | 
						|
            child.setDepth(depth + 1);
 | 
						|
            child.next = ref;
 | 
						|
            if (ref.previous != null)
 | 
						|
              {
 | 
						|
                ref.previous.next = child;
 | 
						|
              }
 | 
						|
            child.previous = ref.previous;
 | 
						|
            ref.previous = child;
 | 
						|
            if (first == ref)
 | 
						|
              {
 | 
						|
                first = child;
 | 
						|
              }
 | 
						|
            // index renumbering
 | 
						|
            for (DomNode ctx = child; ctx != null; ctx = ctx.next)
 | 
						|
              {
 | 
						|
                ctx.index = i++;
 | 
						|
              }
 | 
						|
 | 
						|
            if (reportMutations)
 | 
						|
              {
 | 
						|
                insertionEvent(null, child);
 | 
						|
              }
 | 
						|
            length++;
 | 
						|
          }
 | 
						|
 | 
						|
        return child;
 | 
						|
      }
 | 
						|
    catch (ClassCastException e)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 | 
						|
                                  null, newChild, 0);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Replaces the specified node in this node's list of children.
 | 
						|
   * Document subclasses must override this to test the restrictions
 | 
						|
   * that there be only one element and document type child.
 | 
						|
   *
 | 
						|
   * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
 | 
						|
   * reported.  Will cause another DOMNodeRemoved event to be reported if
 | 
						|
   * the newChild parameter already has a parent.  These events may be
 | 
						|
   * delivered in any order, except that the event reporting removal
 | 
						|
   * from such an existing parent will always be delivered before the
 | 
						|
   * event reporting its re-insertion as a child of some other node.
 | 
						|
   * The order in which children are removed and inserted is implementation
 | 
						|
   * specific.
 | 
						|
   *
 | 
						|
   * <p> If your application needs to depend on the in which those removal
 | 
						|
   * and insertion events are delivered, don't use this API.  Instead,
 | 
						|
   * invoke the removeChild and insertBefore methods directly, to guarantee
 | 
						|
   * a specific delivery order.  Similarly, don't use document fragments,
 | 
						|
   * Otherwise your application code may not work on a DOM which implements
 | 
						|
   * this method differently.
 | 
						|
   *
 | 
						|
   * <p> If this DOM has been compiled without mutation event support,
 | 
						|
   * these events will not be reported.
 | 
						|
   */
 | 
						|
  public Node replaceChild(Node newChild, Node refChild)
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode child = (DomNode) newChild;
 | 
						|
        DomNode ref = (DomNode) refChild;
 | 
						|
 | 
						|
        DomEvent.DomMutationEvent event = getMutationEvent();
 | 
						|
        boolean doFree = (event != null);
 | 
						|
 | 
						|
        if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
 | 
						|
          {
 | 
						|
            // Append all nodes in the fragment to this node
 | 
						|
            for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 | 
						|
              {
 | 
						|
                checkMisc(ctx);
 | 
						|
              }
 | 
						|
            if (ref == null || ref.parent != this)
 | 
						|
              {
 | 
						|
                throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 | 
						|
                                          null, ref, 0);
 | 
						|
              }
 | 
						|
 | 
						|
            if (reportMutations)
 | 
						|
              {
 | 
						|
                removalEvent(event, ref);
 | 
						|
              }
 | 
						|
            length--;
 | 
						|
            length += child.length;
 | 
						|
 | 
						|
            if (child.length == 0)
 | 
						|
              {
 | 
						|
                // Removal
 | 
						|
                if (ref.previous != null)
 | 
						|
                  {
 | 
						|
                    ref.previous.next = ref.next;
 | 
						|
                  }
 | 
						|
                if (ref.next != null)
 | 
						|
                  {
 | 
						|
                    ref.next.previous = ref.previous;
 | 
						|
                  }
 | 
						|
                if (first == ref)
 | 
						|
                  {
 | 
						|
                    first = ref.next;
 | 
						|
                  }
 | 
						|
                if (last == ref)
 | 
						|
                  {
 | 
						|
                    last = ref.previous;
 | 
						|
                  }
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                int i = ref.index;
 | 
						|
                for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 | 
						|
                  {
 | 
						|
                    // Insertion
 | 
						|
                    ctx.parent = this;
 | 
						|
                    ctx.index = i++;
 | 
						|
                    ctx.setDepth(ref.depth);
 | 
						|
                    if (ctx == child.first)
 | 
						|
                      {
 | 
						|
                        ctx.previous = ref.previous;
 | 
						|
                      }
 | 
						|
                    if (ctx == child.last)
 | 
						|
                      {
 | 
						|
                        ctx.next = ref.next;
 | 
						|
                      }
 | 
						|
                  }
 | 
						|
                if (first == ref)
 | 
						|
                  {
 | 
						|
                    first = child.first;
 | 
						|
                  }
 | 
						|
                if (last == ref)
 | 
						|
                  {
 | 
						|
                    last = child.last;
 | 
						|
                  }
 | 
						|
              }
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            checkMisc(child);
 | 
						|
            if (ref == null || ref.parent != this)
 | 
						|
              {
 | 
						|
                throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 | 
						|
                                          null, ref, 0);
 | 
						|
              }
 | 
						|
 | 
						|
            if (reportMutations)
 | 
						|
              {
 | 
						|
                removalEvent(event, ref);
 | 
						|
              }
 | 
						|
 | 
						|
            if (child.parent != null)
 | 
						|
              {
 | 
						|
                child.parent.removeChild(child);
 | 
						|
              }
 | 
						|
            child.parent = this;
 | 
						|
            child.index = ref.index;
 | 
						|
            child.setDepth(ref.depth);
 | 
						|
            if (ref.previous != null)
 | 
						|
              {
 | 
						|
                ref.previous.next = child;
 | 
						|
              }
 | 
						|
            child.previous = ref.previous;
 | 
						|
            if (ref.next != null)
 | 
						|
              {
 | 
						|
                ref.next.previous = child;
 | 
						|
              }
 | 
						|
            child.next = ref.next;
 | 
						|
            if (first == ref)
 | 
						|
              {
 | 
						|
                first = child;
 | 
						|
              }
 | 
						|
            if (last == ref)
 | 
						|
              {
 | 
						|
                last = child;
 | 
						|
              }
 | 
						|
 | 
						|
            if (reportMutations)
 | 
						|
              {
 | 
						|
                insertionEvent(event, child);
 | 
						|
              }
 | 
						|
            if (doFree)
 | 
						|
              {
 | 
						|
                freeMutationEvent();
 | 
						|
              }
 | 
						|
          }
 | 
						|
        ref.parent = null;
 | 
						|
        ref.index = 0;
 | 
						|
        ref.setDepth(0);
 | 
						|
        ref.previous = null;
 | 
						|
        ref.next = null;
 | 
						|
 | 
						|
        return ref;
 | 
						|
      }
 | 
						|
    catch (ClassCastException e)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 | 
						|
                                  null, newChild, 0);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Removes the specified child from this node's list of children,
 | 
						|
   * or else reports an exception.
 | 
						|
   *
 | 
						|
   * <p> Causes a DOMNodeRemoved mutation event to be reported.
 | 
						|
   *
 | 
						|
   * <p> If this DOM has been compiled without mutation event support,
 | 
						|
   * these events will not be reported.
 | 
						|
   */
 | 
						|
  public Node removeChild(Node refChild)
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode ref = (DomNode) refChild;
 | 
						|
 | 
						|
        if (ref == null || ref.parent != this)
 | 
						|
          {
 | 
						|
            throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 | 
						|
                                      null, ref, 0);
 | 
						|
          }
 | 
						|
        if (readonly && !owner.building)
 | 
						|
          {
 | 
						|
            throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
 | 
						|
                                      null, this, 0);
 | 
						|
          }
 | 
						|
 | 
						|
        for (DomNode child = first; child != null; child = child.next)
 | 
						|
          {
 | 
						|
            if (child == ref)
 | 
						|
              {
 | 
						|
                if (reportMutations)
 | 
						|
                  {
 | 
						|
                    removalEvent(null, child);
 | 
						|
                  }
 | 
						|
 | 
						|
                length--;
 | 
						|
                if (ref.previous != null)
 | 
						|
                  {
 | 
						|
                    ref.previous.next = ref.next;
 | 
						|
                  }
 | 
						|
                if (ref.next != null)
 | 
						|
                  {
 | 
						|
                    ref.next.previous = ref.previous;
 | 
						|
                  }
 | 
						|
                if (first == ref)
 | 
						|
                  {
 | 
						|
                    first = ref.next;
 | 
						|
                  }
 | 
						|
                if (last == ref)
 | 
						|
                  {
 | 
						|
                    last = ref.previous;
 | 
						|
                  }
 | 
						|
                // renumber indices
 | 
						|
                int i = 0;
 | 
						|
                for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
                  {
 | 
						|
                    ctx.index = i++;
 | 
						|
                  }
 | 
						|
                ref.parent = null;
 | 
						|
                ref.setDepth(0);
 | 
						|
                ref.index = 0;
 | 
						|
                ref.previous = null;
 | 
						|
                ref.next = null;
 | 
						|
 | 
						|
                return ref;
 | 
						|
              }
 | 
						|
          }
 | 
						|
        throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 | 
						|
                                  "that's no child of mine", refChild, 0);
 | 
						|
      }
 | 
						|
    catch (ClassCastException e)
 | 
						|
      {
 | 
						|
        throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 | 
						|
                                  null, refChild, 0);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1 (NodeList)</b>
 | 
						|
   * Returns the item with the specified index in this NodeList,
 | 
						|
   * else null.
 | 
						|
   */
 | 
						|
  public Node item(int index)
 | 
						|
  {
 | 
						|
    DomNode child = first;
 | 
						|
    int count = 0;
 | 
						|
    while (child != null && count < index)
 | 
						|
      {
 | 
						|
        child = child.next;
 | 
						|
        count++;
 | 
						|
      }
 | 
						|
    return child;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1 (NodeList)</b>
 | 
						|
   * Returns the number of elements in this NodeList.
 | 
						|
   * (Note that many interfaces have a "Length" property, not just
 | 
						|
   * NodeList, and if a node subtype must implement one of those,
 | 
						|
   * it will also need to override getChildNodes.)
 | 
						|
   */
 | 
						|
  public int getLength()
 | 
						|
  {
 | 
						|
    return length;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Minimize extra space consumed by this node to hold children and event
 | 
						|
   * listeners.
 | 
						|
   */
 | 
						|
  public void trimToSize()
 | 
						|
  {
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns the previous sibling, if one is known.
 | 
						|
   */
 | 
						|
  public Node getNextSibling()
 | 
						|
  {
 | 
						|
    return next;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns the previous sibling, if one is known.
 | 
						|
   */
 | 
						|
  public Node getPreviousSibling()
 | 
						|
  {
 | 
						|
    return previous;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns the parent node, if one is known.
 | 
						|
   */
 | 
						|
  public Node getParentNode()
 | 
						|
  {
 | 
						|
    return parent;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Consults the DOM implementation to determine if the requested
 | 
						|
   * feature is supported.  DocumentType subclasses must override
 | 
						|
   * this method, and associate themselves directly with the
 | 
						|
   * DOMImplementation node used.  (This method relies on being able
 | 
						|
   * to access the DOMImplementation from the owner document, but
 | 
						|
   * DocumentType nodes can be created without an owner.)
 | 
						|
   */
 | 
						|
  public boolean isSupported(String feature, String version)
 | 
						|
  {
 | 
						|
    Document            doc = owner;
 | 
						|
    DOMImplementation   impl = null;
 | 
						|
 | 
						|
    if (doc == null && nodeType == DOCUMENT_NODE)
 | 
						|
      {
 | 
						|
        doc = (Document) this;
 | 
						|
      }
 | 
						|
 | 
						|
    if (doc == null)
 | 
						|
      {
 | 
						|
        // possible for DocumentType
 | 
						|
        throw new IllegalStateException ("unbound ownerDocument");
 | 
						|
      }
 | 
						|
 | 
						|
    impl = doc.getImplementation();
 | 
						|
    return impl.hasFeature(feature, version);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1 (modified in L2)</b>
 | 
						|
   * Returns the owner document.  This is only null for Document nodes,
 | 
						|
   * and (new in L2) for DocumentType nodes which have not yet been
 | 
						|
   * associated with the rest of their document.
 | 
						|
   */
 | 
						|
  final public Document getOwnerDocument()
 | 
						|
  {
 | 
						|
    return owner;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Does nothing; this must be overridden (along with the
 | 
						|
   * getNodeValue method) for nodes with a non-null defined value.
 | 
						|
   */
 | 
						|
  public void setNodeValue(String value)
 | 
						|
  {
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns null; this must be overridden for nodes types with
 | 
						|
   * a defined value, along with the setNodeValue method.
 | 
						|
   */
 | 
						|
  public String getNodeValue()
 | 
						|
  {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /** This forces GCJ compatibility.
 | 
						|
   * Without this method GCJ is unable to compile to byte code.
 | 
						|
   */
 | 
						|
  public final short getNodeType()
 | 
						|
  {
 | 
						|
    return nodeType;
 | 
						|
  }
 | 
						|
 | 
						|
  /** This forces GCJ compatibility.
 | 
						|
   * Without this method GCJ seems unable to natively compile GNUJAXP.
 | 
						|
   */
 | 
						|
  public abstract String getNodeName();
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Does nothing; this must be overridden (along with the
 | 
						|
   * getPrefix method) for element and attribute nodes.
 | 
						|
   */
 | 
						|
  public void setPrefix(String prefix)
 | 
						|
  {
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Returns null; this must be overridden for element and
 | 
						|
   * attribute nodes.
 | 
						|
   */
 | 
						|
  public String getPrefix()
 | 
						|
  {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Returns null; this must be overridden for element and
 | 
						|
   * attribute nodes.
 | 
						|
   */
 | 
						|
  public String getNamespaceURI()
 | 
						|
  {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Returns the node name; this must be overridden for element and
 | 
						|
   * attribute nodes.
 | 
						|
   */
 | 
						|
  public String getLocalName()
 | 
						|
  {
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Returns a clone of this node which optionally includes cloned
 | 
						|
   * versions of child nodes.  Clones are always mutable, except for
 | 
						|
   * entity reference nodes.
 | 
						|
   */
 | 
						|
  public Node cloneNode(boolean deep)
 | 
						|
  {
 | 
						|
    if (deep)
 | 
						|
      {
 | 
						|
        return cloneNodeDeepInternal(true, null);
 | 
						|
      }
 | 
						|
 | 
						|
    DomNode node = (DomNode) clone();
 | 
						|
    if (nodeType == ENTITY_REFERENCE_NODE)
 | 
						|
      {
 | 
						|
        node.makeReadonly();
 | 
						|
      }
 | 
						|
    notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
 | 
						|
    return node;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns a deep clone of this node.
 | 
						|
   */
 | 
						|
  private DomNode cloneNodeDeepInternal(boolean root, DomDocument doc)
 | 
						|
  {
 | 
						|
    DomNode node = (DomNode) clone();
 | 
						|
    boolean building = false; // Never used unless root is true
 | 
						|
    if (root)
 | 
						|
      {
 | 
						|
        doc = (nodeType == DOCUMENT_NODE) ? (DomDocument) node : node.owner;
 | 
						|
        building = doc.building;
 | 
						|
        doc.building = true; // Permit certain structural rules
 | 
						|
      }
 | 
						|
    node.owner = doc;
 | 
						|
    for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
      {
 | 
						|
        DomNode newChild = ctx.cloneNodeDeepInternal(false, doc);
 | 
						|
        node.appendChild(newChild);
 | 
						|
      }
 | 
						|
    if (nodeType == ENTITY_REFERENCE_NODE)
 | 
						|
      {
 | 
						|
        node.makeReadonly();
 | 
						|
      }
 | 
						|
    if (root)
 | 
						|
      {
 | 
						|
        doc.building = building;
 | 
						|
      }
 | 
						|
    notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
 | 
						|
    return node;
 | 
						|
  }
 | 
						|
 | 
						|
  void notifyUserDataHandlers(short op, Node src, Node dst)
 | 
						|
  {
 | 
						|
    if (userDataHandlers != null)
 | 
						|
      {
 | 
						|
        for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
 | 
						|
          {
 | 
						|
            Map.Entry entry = (Map.Entry) i.next();
 | 
						|
            String key = (String) entry.getKey();
 | 
						|
            UserDataHandler handler = (UserDataHandler) entry.getValue();
 | 
						|
            Object data = userData.get(key);
 | 
						|
            handler.handle(op, key, data, src, dst);
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Clones this node; roughly equivalent to cloneNode(false).
 | 
						|
   * Element subclasses must provide a new implementation which
 | 
						|
   * invokes this method to handle the basics, and then arranges
 | 
						|
   * to clone any element attributes directly.  Attribute subclasses
 | 
						|
   * must make similar arrangements, ensuring that existing ties to
 | 
						|
   * elements are broken by cloning.
 | 
						|
   */
 | 
						|
  public Object clone()
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode node = (DomNode) super.clone();
 | 
						|
 | 
						|
        node.parent = null;
 | 
						|
        node.depth = 0;
 | 
						|
        node.index = 0;
 | 
						|
        node.length = 0;
 | 
						|
        node.first = null;
 | 
						|
        node.last = null;
 | 
						|
        node.previous = null;
 | 
						|
        node.next = null;
 | 
						|
 | 
						|
        node.readonly = false;
 | 
						|
        node.listeners = new HashSet();
 | 
						|
        node.nListeners = 0;
 | 
						|
        return node;
 | 
						|
 | 
						|
      }
 | 
						|
    catch (CloneNotSupportedException x)
 | 
						|
      {
 | 
						|
        throw new Error("clone didn't work");
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  // the elements-by-tagname stuff is needed for both
 | 
						|
  // elements and documents ... this is in lieu of a
 | 
						|
  // common base class between Node and NodeNS.
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1</b>
 | 
						|
   * Creates a NodeList giving array-style access to elements with
 | 
						|
   * the specified name.  Access is fastest if indices change by
 | 
						|
   * small values, and the DOM is not modified.
 | 
						|
   */
 | 
						|
  public NodeList getElementsByTagName(String tag)
 | 
						|
  {
 | 
						|
    return new ShadowList(null, tag);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2</b>
 | 
						|
   * Creates a NodeList giving array-style access to elements with
 | 
						|
   * the specified namespace and local name.  Access is fastest if
 | 
						|
   * indices change by small values, and the DOM is not modified.
 | 
						|
   */
 | 
						|
  public NodeList getElementsByTagNameNS(String namespace, String local)
 | 
						|
  {
 | 
						|
    return new ShadowList(namespace, local);
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  //
 | 
						|
  // This shadow class is GC-able even when the live list it shadows
 | 
						|
  // can't be, because of event registration hookups.  Its finalizer
 | 
						|
  // makes that live list become GC-able.
 | 
						|
  //
 | 
						|
  final class ShadowList
 | 
						|
    implements NodeList
 | 
						|
  {
 | 
						|
 | 
						|
    private LiveNodeList liveList;
 | 
						|
 | 
						|
    ShadowList(String ns, String local)
 | 
						|
    {
 | 
						|
      liveList = new LiveNodeList(ns, local);
 | 
						|
    }
 | 
						|
 | 
						|
    public void finalize()
 | 
						|
    {
 | 
						|
      liveList.detach();
 | 
						|
      liveList = null;
 | 
						|
    }
 | 
						|
 | 
						|
    public Node item(int index)
 | 
						|
    {
 | 
						|
      return liveList.item(index);
 | 
						|
    }
 | 
						|
 | 
						|
    public int getLength()
 | 
						|
    {
 | 
						|
      return liveList.getLength();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  final class LiveNodeList
 | 
						|
    implements NodeList, EventListener, NodeFilter
 | 
						|
  {
 | 
						|
 | 
						|
    private final boolean matchAnyURI;
 | 
						|
    private final boolean matchAnyName;
 | 
						|
    private final String elementURI;
 | 
						|
    private final String elementName;
 | 
						|
 | 
						|
    private DomIterator current;
 | 
						|
    private int lastIndex;
 | 
						|
 | 
						|
    LiveNodeList(String uri, String name)
 | 
						|
    {
 | 
						|
      elementURI = uri;
 | 
						|
      elementName = name;
 | 
						|
      matchAnyURI = "*".equals(uri);
 | 
						|
      matchAnyName = "*".equals(name);
 | 
						|
 | 
						|
      DomNode.this.addEventListener("DOMNodeInserted", this, true);
 | 
						|
      DomNode.this.addEventListener("DOMNodeRemoved", this, true);
 | 
						|
    }
 | 
						|
 | 
						|
    void detach()
 | 
						|
    {
 | 
						|
      if (current != null)
 | 
						|
        current.detach();
 | 
						|
      current = null;
 | 
						|
 | 
						|
      DomNode.this.removeEventListener("DOMNodeInserted", this, true);
 | 
						|
      DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
 | 
						|
    }
 | 
						|
 | 
						|
    public short acceptNode(Node element)
 | 
						|
    {
 | 
						|
      if (element == DomNode.this)
 | 
						|
        {
 | 
						|
          return FILTER_SKIP;
 | 
						|
        }
 | 
						|
 | 
						|
      // use namespace-aware matching ...
 | 
						|
      if (elementURI != null)
 | 
						|
        {
 | 
						|
          if (!(matchAnyURI
 | 
						|
                || elementURI.equals(element.getNamespaceURI())))
 | 
						|
            {
 | 
						|
              return FILTER_SKIP;
 | 
						|
            }
 | 
						|
          if (!(matchAnyName
 | 
						|
                || elementName.equals(element.getLocalName())))
 | 
						|
            {
 | 
						|
              return FILTER_SKIP;
 | 
						|
            }
 | 
						|
 | 
						|
          // ... or qName-based kind.
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          if (!(matchAnyName
 | 
						|
                || elementName.equals(element.getNodeName())))
 | 
						|
            {
 | 
						|
              return FILTER_SKIP;
 | 
						|
            }
 | 
						|
        }
 | 
						|
      return FILTER_ACCEPT;
 | 
						|
    }
 | 
						|
 | 
						|
    private DomIterator createIterator()
 | 
						|
    {
 | 
						|
      return new DomIterator(DomNode.this,
 | 
						|
                             NodeFilter.SHOW_ELEMENT,
 | 
						|
                             this,      /* filter */
 | 
						|
                             true       /* expand entity refs */
 | 
						|
                            );
 | 
						|
    }
 | 
						|
 | 
						|
    public void handleEvent(Event e)
 | 
						|
    {
 | 
						|
      MutationEvent     mutation = (MutationEvent) e;
 | 
						|
      Node              related = mutation.getRelatedNode();
 | 
						|
 | 
						|
      // XXX if it's got children ... check all kids too, they
 | 
						|
      // will invalidate our saved index
 | 
						|
 | 
						|
      if (related.getNodeType() != Node.ELEMENT_NODE ||
 | 
						|
          related.getNodeName() != elementName ||
 | 
						|
          related.getNamespaceURI() != elementURI)
 | 
						|
        {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
      if (current != null)
 | 
						|
        current.detach();
 | 
						|
      current = null;
 | 
						|
    }
 | 
						|
 | 
						|
    public Node item(int index)
 | 
						|
    {
 | 
						|
      if (current == null)
 | 
						|
        {
 | 
						|
          current = createIterator();
 | 
						|
          lastIndex = -1;
 | 
						|
        }
 | 
						|
 | 
						|
      // last node or before?  go backwards
 | 
						|
      if (index <= lastIndex) {
 | 
						|
        while (index != lastIndex) {
 | 
						|
          current.previousNode ();
 | 
						|
          lastIndex--;
 | 
						|
        }
 | 
						|
        Node ret = current.previousNode ();
 | 
						|
        current.detach();
 | 
						|
        current = null;
 | 
						|
        return ret;
 | 
						|
      }
 | 
						|
 | 
						|
      // somewhere after last node
 | 
						|
      while (++lastIndex != index)
 | 
						|
        current.nextNode ();
 | 
						|
 | 
						|
      Node ret = current.nextNode ();
 | 
						|
      current.detach();
 | 
						|
      current = null;
 | 
						|
      return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    public int getLength()
 | 
						|
    {
 | 
						|
      int retval = 0;
 | 
						|
      NodeIterator iter = createIterator();
 | 
						|
 | 
						|
      while (iter.nextNode() != null)
 | 
						|
        {
 | 
						|
          retval++;
 | 
						|
        }
 | 
						|
      iter.detach();
 | 
						|
      return retval;
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  //
 | 
						|
  // EventTarget support
 | 
						|
  //
 | 
						|
  static final class ListenerRecord
 | 
						|
  {
 | 
						|
 | 
						|
    String type;
 | 
						|
    EventListener listener;
 | 
						|
    boolean useCapture;
 | 
						|
 | 
						|
    // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
 | 
						|
    // and we can both get rid of "shadow" classes and remove
 | 
						|
    // the need for applications to apply similar trix ... but
 | 
						|
    // JDK 1.2 support isn't generally available yet
 | 
						|
 | 
						|
    ListenerRecord(String type, EventListener listener, boolean useCapture)
 | 
						|
    {
 | 
						|
      this.type = type.intern();
 | 
						|
      this.listener = listener;
 | 
						|
      this.useCapture = useCapture;
 | 
						|
    }
 | 
						|
 | 
						|
    public boolean equals(Object o)
 | 
						|
    {
 | 
						|
      ListenerRecord rec = (ListenerRecord)o;
 | 
						|
      return listener == rec.listener
 | 
						|
        && useCapture == rec.useCapture
 | 
						|
        && type == rec.type;
 | 
						|
    }
 | 
						|
 | 
						|
    public int hashCode()
 | 
						|
    {
 | 
						|
        return listener.hashCode() ^ type.hashCode();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2 (Events)</b>
 | 
						|
   * Returns an instance of the specified type of event object.
 | 
						|
   * Understands about DOM Mutation, HTML, and UI events.
 | 
						|
   *
 | 
						|
   * <p>If the name of the event type begins with "USER-", then an object
 | 
						|
   * implementing the "Event" class will be returned; this provides a
 | 
						|
   * limited facility for application-defined events to use the DOM event
 | 
						|
   * infrastructure.  Alternatively, use one of the standard DOM event
 | 
						|
   * classes and initialize it using use such a "USER-" event type name;
 | 
						|
   * or defin, instantiate, and initialize an application-specific subclass
 | 
						|
   * of DomEvent and pass that to dispatchEvent().
 | 
						|
   *
 | 
						|
   * @param eventType Identifies the particular DOM feature module
 | 
						|
   *    defining the type of event, such as "MutationEvents".
 | 
						|
   *    <em>The event "name" is a different kind of "type".</em>
 | 
						|
   */
 | 
						|
  public Event createEvent(String eventType)
 | 
						|
  {
 | 
						|
    eventType = eventType.toLowerCase();
 | 
						|
 | 
						|
    if ("mutationevents".equals(eventType))
 | 
						|
      {
 | 
						|
        return new DomEvent.DomMutationEvent(null);
 | 
						|
      }
 | 
						|
 | 
						|
    if ("htmlevents".equals(eventType)
 | 
						|
        || "events".equals(eventType)
 | 
						|
        || "user-events".equals(eventType))
 | 
						|
      {
 | 
						|
        return new DomEvent(null);
 | 
						|
      }
 | 
						|
 | 
						|
    if ("uievents".equals(eventType))
 | 
						|
      {
 | 
						|
        return new DomEvent.DomUIEvent(null);
 | 
						|
      }
 | 
						|
 | 
						|
    // mouse events
 | 
						|
 | 
						|
    throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
 | 
						|
                              eventType, null, 0);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2 (Events)</b>
 | 
						|
   * Registers an event listener's interest in a class of events.
 | 
						|
   */
 | 
						|
  public final void addEventListener(String type,
 | 
						|
                                     EventListener listener,
 | 
						|
                                     boolean useCapture)
 | 
						|
  {
 | 
						|
    // prune duplicates
 | 
						|
    ListenerRecord record;
 | 
						|
 | 
						|
    record = new ListenerRecord(type, listener, useCapture);
 | 
						|
    listeners.add(record);
 | 
						|
    nListeners = listeners.size();
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX this exception should be discarded from DOM
 | 
						|
 | 
						|
  // this class can be instantiated, unlike the one in the spec
 | 
						|
  static final class DomEventException
 | 
						|
    extends EventException
 | 
						|
  {
 | 
						|
 | 
						|
    DomEventException()
 | 
						|
    {
 | 
						|
      super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2 (Events)</b>
 | 
						|
   * Delivers an event to all relevant listeners, returning true if the
 | 
						|
   * caller should perform their default action.  Note that the event
 | 
						|
   * must have been provided by the createEvent() method on this
 | 
						|
   * class, else it can't be dispatched.
 | 
						|
   *
 | 
						|
   * @see #createEvent
 | 
						|
   *
 | 
						|
   * @exception NullPointerException When a null event is passed.
 | 
						|
   * @exception ClassCastException When the event wasn't provided by
 | 
						|
   *    the createEvent method, or otherwise isn't a DomEvent.
 | 
						|
   * @exception EventException If the event type wasn't specified
 | 
						|
   */
 | 
						|
  public final boolean dispatchEvent(Event event)
 | 
						|
    throws EventException
 | 
						|
  {
 | 
						|
    DomEvent e = (DomEvent) event;
 | 
						|
    DomNode[] ancestors = null;
 | 
						|
    int ancestorMax = 0;
 | 
						|
    boolean haveDispatchDataLock = false;
 | 
						|
 | 
						|
    if (e.type == null)
 | 
						|
      {
 | 
						|
        throw new DomEventException();
 | 
						|
      }
 | 
						|
 | 
						|
    e.doDefault = true;
 | 
						|
    e.target = this;
 | 
						|
 | 
						|
    //
 | 
						|
    // Typical case:  one nonrecursive dispatchEvent call at a time
 | 
						|
    // for this class.  If that's our case, we can avoid allocating
 | 
						|
    // garbage, which is overall a big win.  Even with advanced GCs
 | 
						|
    // that deal well with short-lived garbage, and wayfast allocators,
 | 
						|
    // it still helps.
 | 
						|
    //
 | 
						|
    // Remember -- EVERY mutation goes though here at least once.
 | 
						|
    //
 | 
						|
    // When populating a DOM tree, trying to send mutation events is
 | 
						|
    // the primary cost; this dominates the critical path.
 | 
						|
    //
 | 
						|
    try
 | 
						|
      {
 | 
						|
        DomNode current;
 | 
						|
        int index;
 | 
						|
        boolean haveAncestorRegistrations = false;
 | 
						|
        ListenerRecord[] notificationSet;
 | 
						|
        int ancestorLen;
 | 
						|
 | 
						|
        synchronized (lockNode)
 | 
						|
          {
 | 
						|
            if (!dispatchDataLock)
 | 
						|
              {
 | 
						|
                haveDispatchDataLock = dispatchDataLock = true;
 | 
						|
                notificationSet = DomNode.notificationSet;
 | 
						|
                ancestors = DomNode.ancestors;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
 | 
						|
                ancestors = new DomNode[ANCESTORS_INIT];
 | 
						|
              }
 | 
						|
            ancestorLen = ancestors.length;
 | 
						|
          }
 | 
						|
 | 
						|
        // Climb to the top of this subtree and handle capture, letting
 | 
						|
        // each node (from the top down) capture until one stops it or
 | 
						|
        // until we get to this one.
 | 
						|
        current = (parent == null) ? this : parent;
 | 
						|
        if (current.depth >= ANCESTORS_INIT)
 | 
						|
          {
 | 
						|
            DomNode[] newants = new DomNode[current.depth + 1];
 | 
						|
            System.arraycopy(ancestors, 0, newants, 0, ancestors.length);
 | 
						|
            ancestors = newants;
 | 
						|
            ancestorLen = ancestors.length;
 | 
						|
          }
 | 
						|
        for (index = 0; index < ancestorLen; index++)
 | 
						|
          {
 | 
						|
            if (current == null || current.depth == 0)
 | 
						|
              break;
 | 
						|
 | 
						|
            if (current.nListeners != 0)
 | 
						|
              {
 | 
						|
                haveAncestorRegistrations = true;
 | 
						|
              }
 | 
						|
            ancestors [index] = current;
 | 
						|
            current = current.parent;
 | 
						|
          }
 | 
						|
        if (current.depth > 0)
 | 
						|
          {
 | 
						|
            throw new RuntimeException("dispatchEvent capture stack size");
 | 
						|
          }
 | 
						|
 | 
						|
        ancestorMax = index;
 | 
						|
        e.stop = false;
 | 
						|
 | 
						|
        if (haveAncestorRegistrations)
 | 
						|
          {
 | 
						|
            e.eventPhase = Event.CAPTURING_PHASE;
 | 
						|
            while (!e.stop && index-- > 0)
 | 
						|
              {
 | 
						|
                current = ancestors [index];
 | 
						|
                if (current.nListeners != 0)
 | 
						|
                  {
 | 
						|
                    notifyNode(e, current, true, notificationSet);
 | 
						|
                  }
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        // Always deliver events to the target node (this)
 | 
						|
        // unless stopPropagation was called.  If we saw
 | 
						|
        // no registrations yet (typical!), we never will.
 | 
						|
        if (!e.stop && nListeners != 0)
 | 
						|
          {
 | 
						|
            e.eventPhase = Event.AT_TARGET;
 | 
						|
            notifyNode (e, this, false, notificationSet);
 | 
						|
          }
 | 
						|
        else if (!haveAncestorRegistrations)
 | 
						|
          {
 | 
						|
            e.stop = true;
 | 
						|
          }
 | 
						|
 | 
						|
        // If the event bubbles and propagation wasn't halted,
 | 
						|
        // walk back up the ancestor list.  Stop bubbling when
 | 
						|
        // any bubbled event handler stops it.
 | 
						|
 | 
						|
        if (!e.stop && e.bubbles)
 | 
						|
          {
 | 
						|
            e.eventPhase = Event.BUBBLING_PHASE;
 | 
						|
            for (index = 0;
 | 
						|
                 !e.stop
 | 
						|
                 && index < ancestorMax
 | 
						|
                 && (current = ancestors[index]) != null;
 | 
						|
                 index++)
 | 
						|
              {
 | 
						|
                if (current.nListeners != 0)
 | 
						|
                  {
 | 
						|
                    notifyNode(e, current, false, notificationSet);
 | 
						|
                  }
 | 
						|
              }
 | 
						|
          }
 | 
						|
        e.eventPhase = 0;
 | 
						|
 | 
						|
        // Caller chooses whether to perform the default
 | 
						|
        // action based on return from this method.
 | 
						|
        return e.doDefault;
 | 
						|
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        if (haveDispatchDataLock)
 | 
						|
          {
 | 
						|
            // synchronize to force write ordering
 | 
						|
            synchronized (lockNode)
 | 
						|
              {
 | 
						|
                // null out refs to ensure they'll be GC'd
 | 
						|
                for (int i = 0; i < ancestorMax; i++)
 | 
						|
                  {
 | 
						|
                    ancestors [i] = null;
 | 
						|
                  }
 | 
						|
                // notificationSet handled by notifyNode
 | 
						|
 | 
						|
                dispatchDataLock = false;
 | 
						|
              }
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  private void notifyNode(DomEvent e,
 | 
						|
                          DomNode current,
 | 
						|
                          boolean capture,
 | 
						|
                          ListenerRecord[] notificationSet)
 | 
						|
  {
 | 
						|
    int count = 0;
 | 
						|
    Iterator iter;
 | 
						|
 | 
						|
    iter = current.listeners.iterator();
 | 
						|
 | 
						|
    // do any of this set of listeners get notified?
 | 
						|
    while (iter.hasNext())
 | 
						|
      {
 | 
						|
        ListenerRecord rec = (ListenerRecord)iter.next();
 | 
						|
 | 
						|
        if (rec.useCapture != capture)
 | 
						|
          {
 | 
						|
            continue;
 | 
						|
          }
 | 
						|
        if (!e.type.equals (rec.type))
 | 
						|
          {
 | 
						|
            continue;
 | 
						|
          }
 | 
						|
        if (count >= notificationSet.length)
 | 
						|
          {
 | 
						|
            // very simple growth algorithm
 | 
						|
            int len = Math.max(notificationSet.length, 1);
 | 
						|
            ListenerRecord[] tmp = new ListenerRecord[len * 2];
 | 
						|
            System.arraycopy(notificationSet, 0, tmp, 0,
 | 
						|
                             notificationSet.length);
 | 
						|
            notificationSet = tmp;
 | 
						|
          }
 | 
						|
        notificationSet[count++] = rec;
 | 
						|
      }
 | 
						|
    iter = null;
 | 
						|
 | 
						|
    // Notify just those listeners
 | 
						|
    e.currentNode = current;
 | 
						|
    for (int i = 0; i < count; i++)
 | 
						|
      {
 | 
						|
        try
 | 
						|
          {
 | 
						|
            iter = current.listeners.iterator();
 | 
						|
            // Late in the DOM CR process (3rd or 4th CR?) the
 | 
						|
            // removeEventListener spec became asymmetric with respect
 | 
						|
            // to addEventListener ... effect is now immediate.
 | 
						|
            while (iter.hasNext())
 | 
						|
              {
 | 
						|
                ListenerRecord rec = (ListenerRecord)iter.next();
 | 
						|
 | 
						|
                if (rec.equals(notificationSet[i]))
 | 
						|
                  {
 | 
						|
                    notificationSet[i].listener.handleEvent(e);
 | 
						|
                    break;
 | 
						|
                  }
 | 
						|
              }
 | 
						|
            iter = null;
 | 
						|
          }
 | 
						|
        catch (Exception x)
 | 
						|
          {
 | 
						|
            // ignore all exceptions
 | 
						|
          }
 | 
						|
        notificationSet[i] = null;              // free for GC
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L2 (Events)</b>
 | 
						|
   * Unregisters an event listener.
 | 
						|
   */
 | 
						|
  public final void removeEventListener(String type,
 | 
						|
                                        EventListener listener,
 | 
						|
                                        boolean useCapture)
 | 
						|
  {
 | 
						|
    listeners.remove(new ListenerRecord(type, listener, useCapture));
 | 
						|
    nListeners = listeners.size();
 | 
						|
    // no exceptions reported
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * <b>DOM L1 (relocated in DOM L2)</b>
 | 
						|
   * In this node and all contained nodes (including attributes if
 | 
						|
   * relevant) merge adjacent text nodes.  This is done while ignoring
 | 
						|
   * text which happens to use CDATA delimiters).
 | 
						|
   */
 | 
						|
  public final void normalize()
 | 
						|
  {
 | 
						|
    // Suspend readonly status
 | 
						|
    boolean saved = readonly;
 | 
						|
    readonly = false;
 | 
						|
    for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
      {
 | 
						|
        boolean saved2 = ctx.readonly;
 | 
						|
        ctx.readonly = false;
 | 
						|
        switch (ctx.nodeType)
 | 
						|
          {
 | 
						|
          case TEXT_NODE:
 | 
						|
          case CDATA_SECTION_NODE:
 | 
						|
            while (ctx.next != null &&
 | 
						|
                   (ctx.next.nodeType == TEXT_NODE ||
 | 
						|
                    ctx.next.nodeType == CDATA_SECTION_NODE))
 | 
						|
              {
 | 
						|
                Text text = (Text) ctx;
 | 
						|
                text.appendData(ctx.next.getNodeValue());
 | 
						|
                removeChild(ctx.next);
 | 
						|
              }
 | 
						|
            break;
 | 
						|
          case ELEMENT_NODE:
 | 
						|
            NamedNodeMap attrs = ctx.getAttributes();
 | 
						|
            int len = attrs.getLength();
 | 
						|
            for (int i = 0; i < len; i++)
 | 
						|
              {
 | 
						|
                DomNode attr = (DomNode) attrs.item(i);
 | 
						|
                boolean saved3 = attr.readonly;
 | 
						|
                attr.readonly = false;
 | 
						|
                attr.normalize();
 | 
						|
                attr.readonly = saved3;
 | 
						|
              }
 | 
						|
            // Fall through
 | 
						|
          case DOCUMENT_NODE:
 | 
						|
          case DOCUMENT_FRAGMENT_NODE:
 | 
						|
          case ATTRIBUTE_NODE:
 | 
						|
          case ENTITY_REFERENCE_NODE:
 | 
						|
            ctx.normalize();
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        ctx.readonly = saved2;
 | 
						|
      }
 | 
						|
    readonly = saved;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true iff node types match, and either (a) both nodes have no
 | 
						|
   * namespace and their getNodeName() values are the same, or (b) both
 | 
						|
   * nodes have the same getNamespaceURI() and same getLocalName() values.
 | 
						|
   *
 | 
						|
   * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
 | 
						|
   * found in a non-normative appendix of the XML Namespaces specification,
 | 
						|
   * is not supported here.  Your application must implement that notion,
 | 
						|
   * typically by not bothering to check nameAndTypeEquals for attributes
 | 
						|
   * without namespace URIs unless you already know their elements are
 | 
						|
   * nameAndTypeEquals.
 | 
						|
   */
 | 
						|
  public boolean nameAndTypeEquals(Node other)
 | 
						|
  {
 | 
						|
    if (other == this)
 | 
						|
      {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    // node types must match
 | 
						|
    if (nodeType != other.getNodeType())
 | 
						|
      {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
    // if both have namespaces, do a "full" comparision
 | 
						|
    // this is a "global" partition
 | 
						|
    String ns1 = this.getNamespaceURI();
 | 
						|
    String ns2 = other.getNamespaceURI();
 | 
						|
 | 
						|
    if (ns1 != null && ns2 != null)
 | 
						|
      {
 | 
						|
        return ns1.equals(ns2) &&
 | 
						|
          equal(getLocalName(), other.getLocalName());
 | 
						|
      }
 | 
						|
 | 
						|
    // if neither has a namespace, this is a "no-namespace" name.
 | 
						|
    if (ns1 == null && ns2 == null)
 | 
						|
      {
 | 
						|
        if (!getNodeName().equals(other.getNodeName()))
 | 
						|
          {
 | 
						|
            return false;
 | 
						|
          }
 | 
						|
        // can test the non-normative "per-element-type" scope here.
 | 
						|
        // if this is an attribute node and both nodes have been bound
 | 
						|
        // to elements (!!), then return the nameAndTypeEquals()
 | 
						|
        // comparison of those elements.
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
 | 
						|
    // otherwise they're unequal: one scoped, one not.
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // DOM Level 3 methods
 | 
						|
 | 
						|
  public String getBaseURI()
 | 
						|
  {
 | 
						|
    return (parent != null) ? parent.getBaseURI() : null;
 | 
						|
  }
 | 
						|
 | 
						|
  public short compareDocumentPosition(Node other)
 | 
						|
    throws DOMException
 | 
						|
  {
 | 
						|
    return (short) compareTo(other);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * DOM nodes have a natural ordering: document order.
 | 
						|
   */
 | 
						|
  public final int compareTo(Object other)
 | 
						|
  {
 | 
						|
    if (other instanceof DomNode)
 | 
						|
      {
 | 
						|
        DomNode n1 = this;
 | 
						|
        DomNode n2 = (DomNode) other;
 | 
						|
        if (n1.owner != n2.owner)
 | 
						|
          {
 | 
						|
            return 0;
 | 
						|
          }
 | 
						|
        int d1 = n1.depth, d2 = n2.depth;
 | 
						|
        int delta = d1 - d2;
 | 
						|
        while (d1 > d2)
 | 
						|
          {
 | 
						|
            n1 = n1.parent;
 | 
						|
            d1--;
 | 
						|
          }
 | 
						|
        while (d2 > d1)
 | 
						|
          {
 | 
						|
            n2 = n2.parent;
 | 
						|
            d2--;
 | 
						|
          }
 | 
						|
        int c = compareTo2(n1, n2);
 | 
						|
        return (c != 0) ? c : delta;
 | 
						|
      }
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Compare two nodes at the same depth.
 | 
						|
   */
 | 
						|
  final int compareTo2(DomNode n1, DomNode n2)
 | 
						|
  {
 | 
						|
    if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
 | 
						|
      {
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
    int c = compareTo2(n1.parent, n2.parent);
 | 
						|
    return (c != 0) ? c : n1.index - n2.index;
 | 
						|
  }
 | 
						|
 | 
						|
  public final String getTextContent()
 | 
						|
    throws DOMException
 | 
						|
  {
 | 
						|
    return getTextContent(true);
 | 
						|
  }
 | 
						|
 | 
						|
  final String getTextContent(boolean topLevel)
 | 
						|
    throws DOMException
 | 
						|
  {
 | 
						|
    switch (nodeType)
 | 
						|
      {
 | 
						|
      case ELEMENT_NODE:
 | 
						|
      case ENTITY_NODE:
 | 
						|
      case ENTITY_REFERENCE_NODE:
 | 
						|
      case DOCUMENT_FRAGMENT_NODE:
 | 
						|
        CPStringBuilder buffer = new CPStringBuilder();
 | 
						|
        for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
          {
 | 
						|
            String textContent = ctx.getTextContent(false);
 | 
						|
            if (textContent != null)
 | 
						|
              {
 | 
						|
                buffer.append(textContent);
 | 
						|
              }
 | 
						|
          }
 | 
						|
        return buffer.toString();
 | 
						|
      case TEXT_NODE:
 | 
						|
      case CDATA_SECTION_NODE:
 | 
						|
        if (((Text) this).isElementContentWhitespace())
 | 
						|
          {
 | 
						|
            return "";
 | 
						|
          }
 | 
						|
        return getNodeValue();
 | 
						|
      case ATTRIBUTE_NODE:
 | 
						|
        return getNodeValue();
 | 
						|
      case COMMENT_NODE:
 | 
						|
      case PROCESSING_INSTRUCTION_NODE:
 | 
						|
        return topLevel ? getNodeValue() : "";
 | 
						|
      default:
 | 
						|
        return null;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  public void setTextContent(String textContent)
 | 
						|
    throws DOMException
 | 
						|
  {
 | 
						|
    switch (nodeType)
 | 
						|
      {
 | 
						|
      case ELEMENT_NODE:
 | 
						|
      case ATTRIBUTE_NODE:
 | 
						|
      case ENTITY_NODE:
 | 
						|
      case ENTITY_REFERENCE_NODE:
 | 
						|
      case DOCUMENT_FRAGMENT_NODE:
 | 
						|
        for (DomNode ctx = first; ctx != null; )
 | 
						|
          {
 | 
						|
            DomNode n = ctx.next;
 | 
						|
            removeChild(ctx);
 | 
						|
            ctx = n;
 | 
						|
          }
 | 
						|
        if (textContent != null)
 | 
						|
          {
 | 
						|
            Text text = owner.createTextNode(textContent);
 | 
						|
            appendChild(text);
 | 
						|
          }
 | 
						|
        break;
 | 
						|
      case TEXT_NODE:
 | 
						|
      case CDATA_SECTION_NODE:
 | 
						|
      case COMMENT_NODE:
 | 
						|
      case PROCESSING_INSTRUCTION_NODE:
 | 
						|
        setNodeValue(textContent);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  public boolean isSameNode(Node other)
 | 
						|
  {
 | 
						|
    return this == other;
 | 
						|
  }
 | 
						|
 | 
						|
  public String lookupPrefix(String namespaceURI)
 | 
						|
  {
 | 
						|
    return (parent == null || parent == owner) ? null :
 | 
						|
      parent.lookupPrefix(namespaceURI);
 | 
						|
  }
 | 
						|
 | 
						|
  public boolean isDefaultNamespace(String namespaceURI)
 | 
						|
  {
 | 
						|
    return (parent == null || parent == owner) ? false :
 | 
						|
      parent.isDefaultNamespace(namespaceURI);
 | 
						|
  }
 | 
						|
 | 
						|
  public String lookupNamespaceURI(String prefix)
 | 
						|
  {
 | 
						|
    return (parent == null || parent == owner) ? null :
 | 
						|
      parent.lookupNamespaceURI(prefix);
 | 
						|
  }
 | 
						|
 | 
						|
  public boolean isEqualNode(Node arg)
 | 
						|
  {
 | 
						|
    if (this == arg)
 | 
						|
      return true;
 | 
						|
    if (arg == null)
 | 
						|
      return false;
 | 
						|
    if (nodeType != arg.getNodeType())
 | 
						|
      return false;
 | 
						|
    switch (nodeType)
 | 
						|
      {
 | 
						|
      case ELEMENT_NODE:
 | 
						|
      case ATTRIBUTE_NODE:
 | 
						|
        if (!equal(getLocalName(), arg.getLocalName()) ||
 | 
						|
            !equal(getNamespaceURI(), arg.getNamespaceURI()))
 | 
						|
          return false;
 | 
						|
        break;
 | 
						|
      case PROCESSING_INSTRUCTION_NODE:
 | 
						|
        if (!equal(getNodeName(), arg.getNodeName()) ||
 | 
						|
            !equal(getNodeValue(), arg.getNodeValue()))
 | 
						|
          return false;
 | 
						|
        break;
 | 
						|
      case COMMENT_NODE:
 | 
						|
      case TEXT_NODE:
 | 
						|
      case CDATA_SECTION_NODE:
 | 
						|
        if (!equal(getNodeValue(), arg.getNodeValue()))
 | 
						|
          return false;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    // Children
 | 
						|
    Node argCtx = arg.getFirstChild();
 | 
						|
    getFirstChild(); // because of DomAttr lazy children
 | 
						|
    DomNode ctx = first;
 | 
						|
    for (; ctx != null && argCtx != null; ctx = ctx.next)
 | 
						|
      {
 | 
						|
        if (nodeType == DOCUMENT_NODE)
 | 
						|
          {
 | 
						|
            // Ignore whitespace outside document element
 | 
						|
            while (ctx != null && ctx.nodeType == TEXT_NODE)
 | 
						|
              ctx = ctx.next;
 | 
						|
            while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
 | 
						|
              argCtx = argCtx.getNextSibling();
 | 
						|
            if (ctx == null && argCtx != null)
 | 
						|
              return false;
 | 
						|
            else if (argCtx == null && ctx != null)
 | 
						|
              return false;
 | 
						|
          }
 | 
						|
        if (!ctx.isEqualNode(argCtx))
 | 
						|
          return false;
 | 
						|
        argCtx = argCtx.getNextSibling();
 | 
						|
      }
 | 
						|
    if (ctx != null || argCtx != null)
 | 
						|
      return false;
 | 
						|
 | 
						|
    // TODO DocumentType
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  boolean equal(String arg1, String arg2)
 | 
						|
  {
 | 
						|
    return ((arg1 == null && arg2 == null) ||
 | 
						|
            (arg1 != null && arg1.equals(arg2)));
 | 
						|
  }
 | 
						|
 | 
						|
  public Object getFeature(String feature, String version)
 | 
						|
  {
 | 
						|
    DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
 | 
						|
      ((Document) this).getImplementation() : owner.getImplementation();
 | 
						|
    if (impl.hasFeature(feature, version))
 | 
						|
      {
 | 
						|
        return this;
 | 
						|
      }
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  public Object setUserData(String key, Object data, UserDataHandler handler)
 | 
						|
  {
 | 
						|
    if (userData == null)
 | 
						|
      {
 | 
						|
        userData = new HashMap();
 | 
						|
      }
 | 
						|
    if (handler != null)
 | 
						|
      {
 | 
						|
        if (userDataHandlers == null)
 | 
						|
          {
 | 
						|
            userDataHandlers = new HashMap();
 | 
						|
          }
 | 
						|
        userDataHandlers.put(key, handler);
 | 
						|
      }
 | 
						|
    return userData.put(key, data);
 | 
						|
  }
 | 
						|
 | 
						|
  public Object getUserData(String key)
 | 
						|
  {
 | 
						|
    if (userData == null)
 | 
						|
      {
 | 
						|
        return null;
 | 
						|
      }
 | 
						|
    return userData.get(key);
 | 
						|
  }
 | 
						|
 | 
						|
  public String toString()
 | 
						|
  {
 | 
						|
    String nodeName = getNodeName();
 | 
						|
    String nodeValue = getNodeValue();
 | 
						|
    CPStringBuilder buf = new CPStringBuilder(getClass().getName());
 | 
						|
    buf.append('[');
 | 
						|
    if (nodeName != null)
 | 
						|
      {
 | 
						|
        buf.append(nodeName);
 | 
						|
      }
 | 
						|
    if (nodeValue != null)
 | 
						|
      {
 | 
						|
        if (nodeName != null)
 | 
						|
          {
 | 
						|
            buf.append('=');
 | 
						|
          }
 | 
						|
        buf.append('\'');
 | 
						|
        buf.append(encode(nodeValue));
 | 
						|
        buf.append('\'');
 | 
						|
      }
 | 
						|
    buf.append(']');
 | 
						|
    return buf.toString();
 | 
						|
  }
 | 
						|
 | 
						|
  String encode(String value)
 | 
						|
  {
 | 
						|
    CPStringBuilder buf = null;
 | 
						|
    int len = value.length();
 | 
						|
    for (int i = 0; i < len; i++)
 | 
						|
      {
 | 
						|
        char c = value.charAt(i);
 | 
						|
        if (c == '\n')
 | 
						|
          {
 | 
						|
            if (buf == null)
 | 
						|
              {
 | 
						|
                buf = new CPStringBuilder(value.substring(0, i));
 | 
						|
              }
 | 
						|
            buf.append("\\n");
 | 
						|
          }
 | 
						|
        else if (c == '\r')
 | 
						|
          {
 | 
						|
            if (buf == null)
 | 
						|
              {
 | 
						|
                buf = new CPStringBuilder(value.substring(0, i));
 | 
						|
              }
 | 
						|
            buf.append("\\r");
 | 
						|
          }
 | 
						|
        else if (buf != null)
 | 
						|
          {
 | 
						|
            buf.append(c);
 | 
						|
          }
 | 
						|
      }
 | 
						|
    return (buf != null) ? buf.toString() : value;
 | 
						|
  }
 | 
						|
 | 
						|
  String nodeTypeToString(short nodeType)
 | 
						|
  {
 | 
						|
    switch (nodeType)
 | 
						|
      {
 | 
						|
      case ELEMENT_NODE:
 | 
						|
        return "ELEMENT_NODE";
 | 
						|
      case ATTRIBUTE_NODE:
 | 
						|
        return "ATTRIBUTE_NODE";
 | 
						|
      case TEXT_NODE:
 | 
						|
        return "TEXT_NODE";
 | 
						|
      case CDATA_SECTION_NODE:
 | 
						|
        return "CDATA_SECTION_NODE";
 | 
						|
      case DOCUMENT_NODE:
 | 
						|
        return "DOCUMENT_NODE";
 | 
						|
      case DOCUMENT_TYPE_NODE:
 | 
						|
        return "DOCUMENT_TYPE_NODE";
 | 
						|
      case COMMENT_NODE:
 | 
						|
        return "COMMENT_NODE";
 | 
						|
      case PROCESSING_INSTRUCTION_NODE:
 | 
						|
        return "PROCESSING_INSTRUCTION_NODE";
 | 
						|
      case DOCUMENT_FRAGMENT_NODE:
 | 
						|
        return "DOCUMENT_FRAGMENT_NODE";
 | 
						|
      case ENTITY_NODE:
 | 
						|
        return "ENTITY_NODE";
 | 
						|
      case ENTITY_REFERENCE_NODE:
 | 
						|
        return "ENTITY_REFERENCE_NODE";
 | 
						|
      case NOTATION_NODE:
 | 
						|
        return "NOTATION_NODE";
 | 
						|
      default:
 | 
						|
        return "UNKNOWN";
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  public void list(java.io.PrintStream out, int indent)
 | 
						|
  {
 | 
						|
    for (int i = 0; i < indent; i++)
 | 
						|
      out.print(" ");
 | 
						|
    out.println(toString());
 | 
						|
    for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 | 
						|
      ctx.list(out, indent + 1);
 | 
						|
  }
 | 
						|
 | 
						|
}
 |