View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
7    *
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package com.sun.org.apache.xerces.internal.dom;
22  
23  import java.io.Serializable;
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  
28  import org.w3c.dom.DOMException;
29  import org.w3c.dom.Document;
30  import org.w3c.dom.Node;
31  import org.w3c.dom.NodeList;
32  import org.w3c.dom.UserDataHandler;
33  
34  /**
35   * ParentNode inherits from ChildNode and adds the capability of having child
36   * nodes. Not every node in the DOM can have children, so only nodes that can
37   * should inherit from this class and pay the price for it.
38   * <P>
39   * ParentNode, just like NodeImpl, also implements NodeList, so it can
40   * return itself in response to the getChildNodes() query. This eliminiates
41   * the need for a separate ChildNodeList object. Note that this is an
42   * IMPLEMENTATION DETAIL; applications should _never_ assume that
43   * this identity exists. On the other hand, subclasses may need to override
44   * this, in case of conflicting names. This is the case for the classes
45   * HTMLSelectElementImpl and HTMLFormElementImpl of the HTML DOM.
46   * <P>
47   * While we have a direct reference to the first child, the last child is
48   * stored as the previous sibling of the first child. First child nodes are
49   * marked as being so, and getNextSibling hides this fact.
50   * <P>Note: Not all parent nodes actually need to also be a child. At some
51   * point we used to have ParentNode inheriting from NodeImpl and another class
52   * called ChildAndParentNode that inherited from ChildNode. But due to the lack
53   * of multiple inheritance a lot of code had to be duplicated which led to a
54   * maintenance nightmare. At the same time only a few nodes (Document,
55   * DocumentFragment, Entity, and Attribute) cannot be a child so the gain in
56   * memory wasn't really worth it. The only type for which this would be the
57   * case is Attribute, but we deal with there in another special way, so this is
58   * not applicable.
59   * <p>
60   * This class doesn't directly support mutation events, however, it notifies
61   * the document when mutations are performed so that the document class do so.
62   *
63   * <p><b>WARNING</b>: Some of the code here is partially duplicated in
64   * AttrImpl, be careful to keep these two classes in sync!
65   *
66   * @xerces.internal
67   *
68   * @author Arnaud  Le Hors, IBM
69   * @author Joe Kesselman, IBM
70   * @author Andy Clark, IBM
71   * @version $Id: ParentNode.java,v 1.6 2009/07/21 20:30:28 joehw Exp $
72   */
73  public abstract class ParentNode
74      extends ChildNode {
75  
76      /** Serialization version. */
77      static final long serialVersionUID = 2815829867152120872L;
78  
79      /** Owner document. */
80      protected CoreDocumentImpl ownerDocument;
81  
82      /** First child. */
83      protected ChildNode firstChild = null;
84  
85      // transients
86  
87      /** NodeList cache */
88      protected transient NodeListCache fNodeListCache = null;
89  
90      //
91      // Constructors
92      //
93  
94      /**
95       * No public constructor; only subclasses of ParentNode should be
96       * instantiated, and those normally via a Document's factory methods
97       */
98      protected ParentNode(CoreDocumentImpl ownerDocument) {
99          super(ownerDocument);
100         this.ownerDocument = ownerDocument;
101     }
102 
103     /** Constructor for serialization. */
104     public ParentNode() {}
105 
106     //
107     // NodeList methods
108     //
109 
110     /**
111      * Returns a duplicate of a given node. You can consider this a
112      * generic "copy constructor" for nodes. The newly returned object should
113      * be completely independent of the source object's subtree, so changes
114      * in one after the clone has been made will not affect the other.
115      * <p>
116      * Example: Cloning a Text node will copy both the node and the text it
117      * contains.
118      * <p>
119      * Example: Cloning something that has children -- Element or Attr, for
120      * example -- will _not_ clone those children unless a "deep clone"
121      * has been requested. A shallow clone of an Attr node will yield an
122      * empty Attr of the same name.
123      * <p>
124      * NOTE: Clones will always be read/write, even if the node being cloned
125      * is read-only, to permit applications using only the DOM API to obtain
126      * editable copies of locked portions of the tree.
127      */
128     public Node cloneNode(boolean deep) {
129 
130         if (needsSyncChildren()) {
131             synchronizeChildren();
132         }
133         ParentNode newnode = (ParentNode) super.cloneNode(deep);
134 
135         // set owner document
136         newnode.ownerDocument = ownerDocument;
137 
138         // Need to break the association w/ original kids
139         newnode.firstChild      = null;
140 
141         // invalidate cache for children NodeList
142         newnode.fNodeListCache = null;
143 
144         // Then, if deep, clone the kids too.
145         if (deep) {
146             for (ChildNode child = firstChild;
147                  child != null;
148                  child = child.nextSibling) {
149                 newnode.appendChild(child.cloneNode(true));
150             }
151         }
152 
153         return newnode;
154 
155     } // cloneNode(boolean):Node
156 
157     /**
158      * Find the Document that this Node belongs to (the document in
159      * whose context the Node was created). The Node may or may not
160      * currently be part of that Document's actual contents.
161      */
162     public Document getOwnerDocument() {
163         return ownerDocument;
164     }
165 
166     /**
167      * same as above but returns internal type and this one is not overridden
168      * by CoreDocumentImpl to return null
169      */
170     CoreDocumentImpl ownerDocument() {
171         return ownerDocument;
172     }
173 
174     /**
175      * NON-DOM
176      * set the ownerDocument of this node and its children
177      */
178     void setOwnerDocument(CoreDocumentImpl doc) {
179         if (needsSyncChildren()) {
180             synchronizeChildren();
181         }
182        for (ChildNode child = firstChild;
183              child != null; child = child.nextSibling) {
184              child.setOwnerDocument(doc);
185         }
186         /* setting the owner document of self, after it's children makes the
187            data of children available to the new document. */
188         super.setOwnerDocument(doc);
189         ownerDocument = doc;
190     }
191 
192     /**
193      * Test whether this node has any children. Convenience shorthand
194      * for (Node.getFirstChild()!=null)
195      */
196     public boolean hasChildNodes() {
197         if (needsSyncChildren()) {
198             synchronizeChildren();
199         }
200         return firstChild != null;
201     }
202 
203     /**
204      * Obtain a NodeList enumerating all children of this node. If there
205      * are none, an (initially) empty NodeList is returned.
206      * <p>
207      * NodeLists are "live"; as children are added/removed the NodeList
208      * will immediately reflect those changes. Also, the NodeList refers
209      * to the actual nodes, so changes to those nodes made via the DOM tree
210      * will be reflected in the NodeList and vice versa.
211      * <p>
212      * In this implementation, Nodes implement the NodeList interface and
213      * provide their own getChildNodes() support. Other DOMs may solve this
214      * differently.
215      */
216     public NodeList getChildNodes() {
217 
218         if (needsSyncChildren()) {
219             synchronizeChildren();
220         }
221         return this;
222 
223     } // getChildNodes():NodeList
224 
225     /** The first child of this Node, or null if none. */
226     public Node getFirstChild() {
227 
228         if (needsSyncChildren()) {
229             synchronizeChildren();
230         }
231         return firstChild;
232 
233     }   // getFirstChild():Node
234 
235     /** The last child of this Node, or null if none. */
236     public Node getLastChild() {
237 
238         if (needsSyncChildren()) {
239             synchronizeChildren();
240         }
241         return lastChild();
242 
243     } // getLastChild():Node
244 
245     final ChildNode lastChild() {
246         // last child is stored as the previous sibling of first child
247         return firstChild != null ? firstChild.previousSibling : null;
248     }
249 
250     final void lastChild(ChildNode node) {
251         // store lastChild as previous sibling of first child
252         if (firstChild != null) {
253             firstChild.previousSibling = node;
254         }
255     }
256 
257     /**
258      * Move one or more node(s) to our list of children. Note that this
259      * implicitly removes them from their previous parent.
260      *
261      * @param newChild The Node to be moved to our subtree. As a
262      * convenience feature, inserting a DocumentNode will instead insert
263      * all its children.
264      *
265      * @param refChild Current child which newChild should be placed
266      * immediately before. If refChild is null, the insertion occurs
267      * after all existing Nodes, like appendChild().
268      *
269      * @return newChild, in its new state (relocated, or emptied in the case of
270      * DocumentNode.)
271      *
272      * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
273      * type that shouldn't be a child of this node, or if newChild is an
274      * ancestor of this node.
275      *
276      * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
277      * different owner document than we do.
278      *
279      * @throws DOMException(NOT_FOUND_ERR) if refChild is not a child of
280      * this node.
281      *
282      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
283      * read-only.
284      */
285     public Node insertBefore(Node newChild, Node refChild)
286         throws DOMException {
287         // Tail-call; optimizer should be able to do good things with.
288         return internalInsertBefore(newChild, refChild, false);
289     } // insertBefore(Node,Node):Node
290 
291     /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
292      * to control which mutation events are spawned. This version of the
293      * insertBefore operation allows us to do so. It is not intended
294      * for use by application programs.
295      */
296     Node internalInsertBefore(Node newChild, Node refChild, boolean replace)
297         throws DOMException {
298 
299         boolean errorChecking = ownerDocument.errorChecking;
300 
301         if (newChild.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {
302             // SLOW BUT SAFE: We could insert the whole subtree without
303             // juggling so many next/previous pointers. (Wipe out the
304             // parent's child-list, patch the parent pointers, set the
305             // ends of the list.) But we know some subclasses have special-
306             // case behavior they add to insertBefore(), so we don't risk it.
307             // This approch also takes fewer bytecodes.
308 
309             // NOTE: If one of the children is not a legal child of this
310             // node, throw HIERARCHY_REQUEST_ERR before _any_ of the children
311             // have been transferred. (Alternative behaviors would be to
312             // reparent up to the first failure point or reparent all those
313             // which are acceptable to the target node, neither of which is
314             // as robust. PR-DOM-0818 isn't entirely clear on which it
315             // recommends?????
316 
317             // No need to check kids for right-document; if they weren't,
318             // they wouldn't be kids of that DocFrag.
319             if (errorChecking) {
320                 for (Node kid = newChild.getFirstChild(); // Prescan
321                      kid != null; kid = kid.getNextSibling()) {
322 
323                     if (!ownerDocument.isKidOK(this, kid)) {
324                         throw new DOMException(
325                               DOMException.HIERARCHY_REQUEST_ERR,
326                               DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
327                     }
328                 }
329             }
330 
331             while (newChild.hasChildNodes()) {
332                 insertBefore(newChild.getFirstChild(), refChild);
333             }
334             return newChild;
335         }
336 
337         if (newChild == refChild) {
338             // stupid case that must be handled as a no-op triggering events...
339             refChild = refChild.getNextSibling();
340             removeChild(newChild);
341             insertBefore(newChild, refChild);
342             return newChild;
343         }
344 
345         if (needsSyncChildren()) {
346             synchronizeChildren();
347         }
348 
349         if (errorChecking) {
350             if (isReadOnly()) {
351                 throw new DOMException(
352                               DOMException.NO_MODIFICATION_ALLOWED_ERR,
353                               DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
354             }
355             if (newChild.getOwnerDocument() != ownerDocument && newChild != ownerDocument) {
356                 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
357                             DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
358             }
359             if (!ownerDocument.isKidOK(this, newChild)) {
360                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
361                             DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
362             }
363             // refChild must be a child of this node (or null)
364             if (refChild != null && refChild.getParentNode() != this) {
365                 throw new DOMException(DOMException.NOT_FOUND_ERR,
366                             DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null));
367             }
368 
369             // Prevent cycles in the tree
370             // newChild cannot be ancestor of this Node,
371             // and actually cannot be this
372             if (ownerDocument.ancestorChecking) {
373                 boolean treeSafe = true;
374                 for (NodeImpl a = this; treeSafe && a != null; a = a.parentNode())
375                 {
376                     treeSafe = newChild != a;
377                 }
378                 if(!treeSafe) {
379                     throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
380                                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
381                 }
382             }
383         }
384 
385         // notify document
386         ownerDocument.insertingNode(this, replace);
387 
388         // Convert to internal type, to avoid repeated casting
389         ChildNode newInternal = (ChildNode)newChild;
390 
391         Node oldparent = newInternal.parentNode();
392         if (oldparent != null) {
393             oldparent.removeChild(newInternal);
394         }
395 
396         // Convert to internal type, to avoid repeated casting
397         ChildNode refInternal = (ChildNode)refChild;
398 
399         // Attach up
400         newInternal.ownerNode = this;
401         newInternal.isOwned(true);
402 
403         // Attach before and after
404         // Note: firstChild.previousSibling == lastChild!!
405         if (firstChild == null) {
406             // this our first and only child
407             firstChild = newInternal;
408             newInternal.isFirstChild(true);
409             newInternal.previousSibling = newInternal;
410         }
411         else {
412             if (refInternal == null) {
413                 // this is an append
414                 ChildNode lastChild = firstChild.previousSibling;
415                 lastChild.nextSibling = newInternal;
416                 newInternal.previousSibling = lastChild;
417                 firstChild.previousSibling = newInternal;
418             }
419             else {
420                 // this is an insert
421                 if (refChild == firstChild) {
422                     // at the head of the list
423                     firstChild.isFirstChild(false);
424                     newInternal.nextSibling = firstChild;
425                     newInternal.previousSibling = firstChild.previousSibling;
426                     firstChild.previousSibling = newInternal;
427                     firstChild = newInternal;
428                     newInternal.isFirstChild(true);
429                 }
430                 else {
431                     // somewhere in the middle
432                     ChildNode prev = refInternal.previousSibling;
433                     newInternal.nextSibling = refInternal;
434                     prev.nextSibling = newInternal;
435                     refInternal.previousSibling = newInternal;
436                     newInternal.previousSibling = prev;
437                 }
438             }
439         }
440 
441         changed();
442 
443         // update cached length if we have any
444         if (fNodeListCache != null) {
445             if (fNodeListCache.fLength != -1) {
446                 fNodeListCache.fLength++;
447             }
448             if (fNodeListCache.fChildIndex != -1) {
449                 // if we happen to insert just before the cached node, update
450                 // the cache to the new node to match the cached index
451                 if (fNodeListCache.fChild == refInternal) {
452                     fNodeListCache.fChild = newInternal;
453                 } else {
454                     // otherwise just invalidate the cache
455                     fNodeListCache.fChildIndex = -1;
456                 }
457             }
458         }
459 
460         // notify document
461         ownerDocument.insertedNode(this, newInternal, replace);
462 
463         checkNormalizationAfterInsert(newInternal);
464 
465         return newChild;
466 
467     } // internalInsertBefore(Node,Node,boolean):Node
468 
469     /**
470      * Remove a child from this Node. The removed child's subtree
471      * remains intact so it may be re-inserted elsewhere.
472      *
473      * @return oldChild, in its new state (removed).
474      *
475      * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
476      * this node.
477      *
478      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
479      * read-only.
480      */
481     public Node removeChild(Node oldChild)
482         throws DOMException {
483         // Tail-call, should be optimizable
484         return internalRemoveChild(oldChild, false);
485     } // removeChild(Node) :Node
486 
487     /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
488      * to control which mutation events are spawned. This version of the
489      * removeChild operation allows us to do so. It is not intended
490      * for use by application programs.
491      */
492     Node internalRemoveChild(Node oldChild, boolean replace)
493         throws DOMException {
494 
495         CoreDocumentImpl ownerDocument = ownerDocument();
496         if (ownerDocument.errorChecking) {
497             if (isReadOnly()) {
498                 throw new DOMException(
499                             DOMException.NO_MODIFICATION_ALLOWED_ERR,
500                             DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
501             }
502             if (oldChild != null && oldChild.getParentNode() != this) {
503                 throw new DOMException(DOMException.NOT_FOUND_ERR,
504                             DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null));
505             }
506         }
507 
508         ChildNode oldInternal = (ChildNode) oldChild;
509 
510         // notify document
511         ownerDocument.removingNode(this, oldInternal, replace);
512 
513         // update cached length if we have any
514         if (fNodeListCache != null) {
515             if (fNodeListCache.fLength != -1) {
516                 fNodeListCache.fLength--;
517             }
518             if (fNodeListCache.fChildIndex != -1) {
519                 // if the removed node is the cached node
520                 // move the cache to its (soon former) previous sibling
521                 if (fNodeListCache.fChild == oldInternal) {
522                     fNodeListCache.fChildIndex--;
523                     fNodeListCache.fChild = oldInternal.previousSibling();
524                 } else {
525                     // otherwise just invalidate the cache
526                     fNodeListCache.fChildIndex = -1;
527                 }
528             }
529         }
530 
531         // Patch linked list around oldChild
532         // Note: lastChild == firstChild.previousSibling
533         if (oldInternal == firstChild) {
534             // removing first child
535             oldInternal.isFirstChild(false);
536             firstChild = oldInternal.nextSibling;
537             if (firstChild != null) {
538                 firstChild.isFirstChild(true);
539                 firstChild.previousSibling = oldInternal.previousSibling;
540             }
541         } else {
542             ChildNode prev = oldInternal.previousSibling;
543             ChildNode next = oldInternal.nextSibling;
544             prev.nextSibling = next;
545             if (next == null) {
546                 // removing last child
547                 firstChild.previousSibling = prev;
548             } else {
549                 // removing some other child in the middle
550                 next.previousSibling = prev;
551             }
552         }
553 
554         // Save previous sibling for normalization checking.
555         ChildNode oldPreviousSibling = oldInternal.previousSibling();
556 
557         // Remove oldInternal's references to tree
558         oldInternal.ownerNode       = ownerDocument;
559         oldInternal.isOwned(false);
560         oldInternal.nextSibling     = null;
561         oldInternal.previousSibling = null;
562 
563         changed();
564 
565         // notify document
566         ownerDocument.removedNode(this, replace);
567 
568         checkNormalizationAfterRemove(oldPreviousSibling);
569 
570         return oldInternal;
571 
572     } // internalRemoveChild(Node,boolean):Node
573 
574     /**
575      * Make newChild occupy the location that oldChild used to
576      * have. Note that newChild will first be removed from its previous
577      * parent, if any. Equivalent to inserting newChild before oldChild,
578      * then removing oldChild.
579      *
580      * @return oldChild, in its new state (removed).
581      *
582      * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
583      * type that shouldn't be a child of this node, or if newChild is
584      * one of our ancestors.
585      *
586      * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
587      * different owner document than we do.
588      *
589      * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
590      * this node.
591      *
592      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
593      * read-only.
594      */
595     public Node replaceChild(Node newChild, Node oldChild)
596         throws DOMException {
597         // If Mutation Events are being generated, this operation might
598         // throw aggregate events twice when modifying an Attr -- once
599         // on insertion and once on removal. DOM Level 2 does not specify
600         // this as either desirable or undesirable, but hints that
601         // aggregations should be issued only once per user request.
602 
603         // notify document
604         ownerDocument.replacingNode(this);
605 
606         internalInsertBefore(newChild, oldChild, true);
607         if (newChild != oldChild) {
608             internalRemoveChild(oldChild, true);
609         }
610 
611         // notify document
612         ownerDocument.replacedNode(this);
613 
614         return oldChild;
615     }
616 
617     /*
618      * Get Node text content
619      * @since DOM Level 3
620      */
621     public String getTextContent() throws DOMException {
622         Node child = getFirstChild();
623         if (child != null) {
624             Node next = child.getNextSibling();
625             if (next == null) {
626                 return hasTextContent(child) ? ((NodeImpl) child).getTextContent() : "";
627             }
628             if (fBufferStr == null){
629                 fBufferStr = new StringBuffer();
630             }
631             else {
632                 fBufferStr.setLength(0);
633             }
634             getTextContent(fBufferStr);
635             return fBufferStr.toString();
636         }
637         return "";
638     }
639 
640     // internal method taking a StringBuffer in parameter
641     void getTextContent(StringBuffer buf) throws DOMException {
642         Node child = getFirstChild();
643         while (child != null) {
644             if (hasTextContent(child)) {
645                 ((NodeImpl) child).getTextContent(buf);
646             }
647             child = child.getNextSibling();
648         }
649     }
650 
651     // internal method returning whether to take the given node's text content
652     final boolean hasTextContent(Node child) {
653         return child.getNodeType() != Node.COMMENT_NODE &&
654             child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE &&
655             (child.getNodeType() != Node.TEXT_NODE ||
656              ((TextImpl) child).isIgnorableWhitespace() == false);
657     }
658 
659     /*
660      * Set Node text content
661      * @since DOM Level 3
662      */
663     public void setTextContent(String textContent)
664         throws DOMException {
665         // get rid of any existing children
666         Node child;
667         while ((child = getFirstChild()) != null) {
668             removeChild(child);
669         }
670         // create a Text node to hold the given content
671         if (textContent != null && textContent.length() != 0){
672             appendChild(ownerDocument().createTextNode(textContent));
673         }
674     }
675 
676     //
677     // NodeList methods
678     //
679 
680     /**
681      * Count the immediate children of this node.  Use to implement
682      * NodeList.getLength().
683      * @return int
684      */
685     private int nodeListGetLength() {
686 
687         if (fNodeListCache == null) {
688             // get rid of trivial cases
689             if (firstChild == null) {
690                 return 0;
691             }
692             if (firstChild == lastChild()) {
693                 return 1;
694             }
695             // otherwise request a cache object
696             fNodeListCache = ownerDocument.getNodeListCache(this);
697         }
698         if (fNodeListCache.fLength == -1) { // is the cached length invalid ?
699             int l;
700             ChildNode n;
701             // start from the cached node if we have one
702             if (fNodeListCache.fChildIndex != -1 &&
703                 fNodeListCache.fChild != null) {
704                 l = fNodeListCache.fChildIndex;
705                 n = fNodeListCache.fChild;
706             } else {
707                 n = firstChild;
708                 l = 0;
709             }
710             while (n != null) {
711                 l++;
712                 n = n.nextSibling;
713             }
714             fNodeListCache.fLength = l;
715         }
716 
717         return fNodeListCache.fLength;
718 
719     } // nodeListGetLength():int
720 
721     /**
722      * NodeList method: Count the immediate children of this node
723      * @return int
724      */
725     public int getLength() {
726         return nodeListGetLength();
727     }
728 
729     /**
730      * Return the Nth immediate child of this node, or null if the index is
731      * out of bounds.  Use to implement NodeList.item().
732      * @param index int
733      */
734     private Node nodeListItem(int index) {
735 
736         if (fNodeListCache == null) {
737             // get rid of trivial case
738             if (firstChild == lastChild()) {
739                 return index == 0 ? firstChild : null;
740             }
741             // otherwise request a cache object
742             fNodeListCache = ownerDocument.getNodeListCache(this);
743         }
744         int i = fNodeListCache.fChildIndex;
745         ChildNode n = fNodeListCache.fChild;
746         boolean firstAccess = true;
747         // short way
748         if (i != -1 && n != null) {
749             firstAccess = false;
750             if (i < index) {
751                 while (i < index && n != null) {
752                     i++;
753                     n = n.nextSibling;
754                 }
755             }
756             else if (i > index) {
757                 while (i > index && n != null) {
758                     i--;
759                     n = n.previousSibling();
760                 }
761             }
762         }
763         else {
764             // long way
765             if (index < 0) {
766                 return null;
767             }
768             n = firstChild;
769             for (i = 0; i < index && n != null; i++) {
770                 n = n.nextSibling;
771             }
772         }
773 
774         // release cache if reaching last child or first child
775         if (!firstAccess && (n == firstChild || n == lastChild())) {
776             fNodeListCache.fChildIndex = -1;
777             fNodeListCache.fChild = null;
778             ownerDocument.freeNodeListCache(fNodeListCache);
779             // we can keep using the cache until it is actually reused
780             // fNodeListCache will be nulled by the pool (document) if that
781             // happens.
782             // fNodeListCache = null;
783         }
784         else {
785             // otherwise update it
786             fNodeListCache.fChildIndex = i;
787             fNodeListCache.fChild = n;
788         }
789         return n;
790 
791     } // nodeListItem(int):Node
792 
793     /**
794      * NodeList method: Return the Nth immediate child of this node, or
795      * null if the index is out of bounds.
796      * @return org.w3c.dom.Node
797      * @param index int
798      */
799     public Node item(int index) {
800         return nodeListItem(index);
801     } // item(int):Node
802 
803     /**
804      * Create a NodeList to access children that is use by subclass elements
805      * that have methods named getLength() or item(int).  ChildAndParentNode
806      * optimizes getChildNodes() by implementing NodeList itself.  However if
807      * a subclass Element implements methods with the same name as the NodeList
808      * methods, they will override the actually methods in this class.
809      * <p>
810      * To use this method, the subclass should implement getChildNodes() and
811      * have it call this method.  The resulting NodeList instance maybe
812      * shared and cached in a transient field, but the cached value must be
813      * cleared if the node is cloned.
814      */
815     protected final NodeList getChildNodesUnoptimized() {
816         if (needsSyncChildren()) {
817             synchronizeChildren();
818         }
819         return new NodeList() {
820                 /**
821                  * @see NodeList.getLength()
822                  */
823                 public int getLength() {
824                     return nodeListGetLength();
825                 } // getLength():int
826 
827                 /**
828                  * @see NodeList.item(int)
829                  */
830                 public Node item(int index) {
831                     return nodeListItem(index);
832                 } // item(int):Node
833             };
834     } // getChildNodesUnoptimized():NodeList
835 
836     //
837     // DOM2: methods, getters, setters
838     //
839 
840     /**
841      * Override default behavior to call normalize() on this Node's
842      * children. It is up to implementors or Node to override normalize()
843      * to take action.
844      */
845     public void normalize() {
846         // No need to normalize if already normalized.
847         if (isNormalized()) {
848             return;
849         }
850         if (needsSyncChildren()) {
851             synchronizeChildren();
852         }
853         ChildNode kid;
854         for (kid = firstChild; kid != null; kid = kid.nextSibling) {
855             kid.normalize();
856         }
857         isNormalized(true);
858     }
859 
860     /**
861      * DOM Level 3 WD- Experimental.
862      * Override inherited behavior from NodeImpl to support deep equal.
863      */
864     public boolean isEqualNode(Node arg) {
865         if (!super.isEqualNode(arg)) {
866             return false;
867         }
868         // there are many ways to do this test, and there isn't any way
869         // better than another. Performance may vary greatly depending on
870         // the implementations involved. This one should work fine for us.
871         Node child1 = getFirstChild();
872         Node child2 = arg.getFirstChild();
873         while (child1 != null && child2 != null) {
874             if (!((NodeImpl) child1).isEqualNode(child2)) {
875                 return false;
876             }
877             child1 = child1.getNextSibling();
878             child2 = child2.getNextSibling();
879         }
880         if (child1 != child2) {
881             return false;
882         }
883         return true;
884     }
885 
886     //
887     // Public methods
888     //
889 
890     /**
891      * Override default behavior so that if deep is true, children are also
892      * toggled.
893      * @see Node
894      * <P>
895      * Note: this will not change the state of an EntityReference or its
896      * children, which are always read-only.
897      */
898     public void setReadOnly(boolean readOnly, boolean deep) {
899 
900         super.setReadOnly(readOnly, deep);
901 
902         if (deep) {
903 
904             if (needsSyncChildren()) {
905                 synchronizeChildren();
906             }
907 
908             // Recursively set kids
909             for (ChildNode mykid = firstChild;
910                  mykid != null;
911                  mykid = mykid.nextSibling) {
912                 if (mykid.getNodeType() != Node.ENTITY_REFERENCE_NODE) {
913                     mykid.setReadOnly(readOnly,true);
914                 }
915             }
916         }
917     } // setReadOnly(boolean,boolean)
918 
919     //
920     // Protected methods
921     //
922 
923     /**
924      * Override this method in subclass to hook in efficient
925      * internal data structure.
926      */
927     protected void synchronizeChildren() {
928         // By default just change the flag to avoid calling this method again
929         needsSyncChildren(false);
930     }
931 
932     /**
933      * Checks the normalized state of this node after inserting a child.
934      * If the inserted child causes this node to be unnormalized, then this
935      * node is flagged accordingly.
936      * The conditions for changing the normalized state are:
937      * <ul>
938      * <li>The inserted child is a text node and one of its adjacent siblings
939      * is also a text node.
940      * <li>The inserted child is is itself unnormalized.
941      * </ul>
942      *
943      * @param insertedChild the child node that was inserted into this node
944      *
945      * @throws NullPointerException if the inserted child is <code>null</code>
946      */
947     void checkNormalizationAfterInsert(ChildNode insertedChild) {
948         // See if insertion caused this node to be unnormalized.
949         if (insertedChild.getNodeType() == Node.TEXT_NODE) {
950             ChildNode prev = insertedChild.previousSibling();
951             ChildNode next = insertedChild.nextSibling;
952             // If an adjacent sibling of the new child is a text node,
953             // flag this node as unnormalized.
954             if ((prev != null && prev.getNodeType() == Node.TEXT_NODE) ||
955                 (next != null && next.getNodeType() == Node.TEXT_NODE)) {
956                 isNormalized(false);
957             }
958         }
959         else {
960             // If the new child is not normalized,
961             // then this node is inherently not normalized.
962             if (!insertedChild.isNormalized()) {
963                 isNormalized(false);
964             }
965         }
966     } // checkNormalizationAfterInsert(ChildNode)
967 
968     /**
969      * Checks the normalized of this node after removing a child.
970      * If the removed child causes this node to be unnormalized, then this
971      * node is flagged accordingly.
972      * The conditions for changing the normalized state are:
973      * <ul>
974      * <li>The removed child had two adjacent siblings that were text nodes.
975      * </ul>
976      *
977      * @param previousSibling the previous sibling of the removed child, or
978      * <code>null</code>
979      */
980     void checkNormalizationAfterRemove(ChildNode previousSibling) {
981         // See if removal caused this node to be unnormalized.
982         // If the adjacent siblings of the removed child were both text nodes,
983         // flag this node as unnormalized.
984         if (previousSibling != null &&
985             previousSibling.getNodeType() == Node.TEXT_NODE) {
986 
987             ChildNode next = previousSibling.nextSibling;
988             if (next != null && next.getNodeType() == Node.TEXT_NODE) {
989                 isNormalized(false);
990             }
991         }
992     } // checkNormalizationAfterRemove(Node)
993 
994     //
995     // Serialization methods
996     //
997 
998     /** Serialize object. */
999     private void writeObject(ObjectOutputStream out) throws IOException {
1000 
1001         // synchronize chilren
1002         if (needsSyncChildren()) {
1003             synchronizeChildren();
1004         }
1005         // write object
1006         out.defaultWriteObject();
1007 
1008     } // writeObject(ObjectOutputStream)
1009 
1010     /** Deserialize object. */
1011     private void readObject(ObjectInputStream ois)
1012         throws ClassNotFoundException, IOException {
1013 
1014         // perform default deseralization
1015         ois.defaultReadObject();
1016 
1017         // hardset synchildren - so we don't try to sync - it does not make any
1018         // sense to try to synchildren when we just deserialize object.
1019         needsSyncChildren(false);
1020 
1021     } // readObject(ObjectInputStream)
1022 
1023     /*
1024      * a class to store some user data along with its handler
1025      */
1026     class UserDataRecord implements Serializable {
1027         /** Serialization version. */
1028         private static final long serialVersionUID = 3258126977134310455L;
1029 
1030         Object fData;
1031         UserDataHandler fHandler;
1032         UserDataRecord(Object data, UserDataHandler handler) {
1033             fData = data;
1034             fHandler = handler;
1035         }
1036     }
1037 } // class ParentNode