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);
 | |
|   }
 | |
| 
 | |
| }
 |