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