001: /* $Id: ParentNode.java,v 1.27 2001/08/10 01:18:16 lehors Exp $ */
002: /*
003: * The Apache Software License, Version 1.1
004: *
005: *
006: * Copyright (c) 1999-2001 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution,
022: * if any, must include the following acknowledgment:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgment may appear in the software itself,
026: * if and wherever such third-party acknowledgments normally appear.
027: *
028: * 4. The names "Xerces" and "Apache Software Foundation" must
029: * not be used to endorse or promote products derived from this
030: * software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache",
034: * nor may "Apache" appear in their name, without prior written
035: * permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation and was
053: * originally based on software copyright (c) 1999, International
054: * Business Machines, Inc., http://www.apache.org. For more
055: * information on the Apache Software Foundation, please see
056: * <http://www.apache.org/>.
057: */
058:
059: package org.apache.xerces.dom;
060:
061: import java.io.IOException;
062: import java.io.ObjectInputStream;
063: import java.io.ObjectOutputStream;
064:
065: import org.w3c.dom.DOMException;
066: import org.w3c.dom.Document;
067: import org.w3c.dom.Node;
068: import org.w3c.dom.NodeList;
069:
070: /**
071: * ParentNode inherits from ChildNode and adds the capability of having child
072: * nodes. Not every node in the DOM can have children, so only nodes that can
073: * should inherit from this class and pay the price for it.
074: * <P>
075: * ParentNode, just like NodeImpl, also implements NodeList, so it can
076: * return itself in response to the getChildNodes() query. This eliminiates
077: * the need for a separate ChildNodeList object. Note that this is an
078: * IMPLEMENTATION DETAIL; applications should _never_ assume that
079: * this identity exists. On the other hand, subclasses may need to override
080: * this, in case of conflicting names. This is the case for the classes
081: * HTMLSelectElementImpl and HTMLFormElementImpl of the HTML DOM.
082: * <P>
083: * While we have a direct reference to the first child, the last child is
084: * stored as the previous sibling of the first child. First child nodes are
085: * marked as being so, and getNextSibling hides this fact.
086: * <P>Note: Not all parent nodes actually need to also be a child. At some
087: * point we used to have ParentNode inheriting from NodeImpl and another class
088: * called ChildAndParentNode that inherited from ChildNode. But due to the lack
089: * of multiple inheritance a lot of code had to be duplicated which led to a
090: * maintenance nightmare. At the same time only a few nodes (Document,
091: * DocumentFragment, Entity, and Attribute) cannot be a child so the gain in
092: * memory wasn't really worth it. The only type for which this would be the
093: * case is Attribute, but we deal with there in another special way, so this is
094: * not applicable.
095: * <p>
096: * This class doesn't directly support mutation events, however, it notifies
097: * the document when mutations are performed so that the document class do so.
098: *
099: * <p><b>WARNING</b>: Some of the code here is partially duplicated in
100: * AttrImpl, be careful to keep these two classes in sync!
101: *
102: * @author Arnaud Le Hors, IBM
103: * @author Joe Kesselman, IBM
104: * @author Andy Clark, IBM
105: */
106: public abstract class ParentNode extends ChildNode {
107:
108: /** Serialization version. */
109: static final long serialVersionUID = 2815829867152120872L;
110:
111: /** Owner document. */
112: protected CoreDocumentImpl ownerDocument;
113:
114: /** First child. */
115: protected ChildNode firstChild = null;
116:
117: // transients
118:
119: /** Cached node list length. */
120: protected transient int fCachedLength = -1;
121:
122: /** Last requested node. */
123: protected transient ChildNode fCachedChild;
124:
125: /** Last requested node index. */
126: protected transient int fCachedChildIndex = -1;
127:
128: //
129: // Constructors
130: //
131:
132: /**
133: * No public constructor; only subclasses of ParentNode should be
134: * instantiated, and those normally via a Document's factory methods
135: */
136: protected ParentNode(CoreDocumentImpl ownerDocument) {
137: super (ownerDocument);
138: this .ownerDocument = ownerDocument;
139: }
140:
141: /** Constructor for serialization. */
142: public ParentNode() {
143: }
144:
145: //
146: // NodeList methods
147: //
148:
149: /**
150: * Returns a duplicate of a given node. You can consider this a
151: * generic "copy constructor" for nodes. The newly returned object should
152: * be completely independent of the source object's subtree, so changes
153: * in one after the clone has been made will not affect the other.
154: * <p>
155: * Example: Cloning a Text node will copy both the node and the text it
156: * contains.
157: * <p>
158: * Example: Cloning something that has children -- Element or Attr, for
159: * example -- will _not_ clone those children unless a "deep clone"
160: * has been requested. A shallow clone of an Attr node will yield an
161: * empty Attr of the same name.
162: * <p>
163: * NOTE: Clones will always be read/write, even if the node being cloned
164: * is read-only, to permit applications using only the DOM API to obtain
165: * editable copies of locked portions of the tree.
166: */
167: public Node cloneNode(boolean deep) {
168:
169: if (needsSyncChildren()) {
170: synchronizeChildren();
171: }
172: ParentNode newnode = (ParentNode) super .cloneNode(deep);
173:
174: // set owner document
175: newnode.ownerDocument = ownerDocument;
176:
177: // Need to break the association w/ original kids
178: newnode.firstChild = null;
179:
180: // invalidate cache for children NodeList
181: newnode.fCachedChildIndex = -1;
182: newnode.fCachedLength = -1;
183:
184: // Then, if deep, clone the kids too.
185: if (deep) {
186: for (ChildNode child = firstChild; child != null; child = child.nextSibling) {
187: newnode.appendChild(child.cloneNode(true));
188: }
189: }
190:
191: return newnode;
192:
193: } // cloneNode(boolean):Node
194:
195: /**
196: * Find the Document that this Node belongs to (the document in
197: * whose context the Node was created). The Node may or may not
198: * currently be part of that Document's actual contents.
199: */
200: public Document getOwnerDocument() {
201: return ownerDocument;
202: }
203:
204: /**
205: * same as above but returns internal type and this one is not overridden
206: * by CoreDocumentImpl to return null
207: */
208: CoreDocumentImpl ownerDocument() {
209: return ownerDocument;
210: }
211:
212: /**
213: * NON-DOM
214: * set the ownerDocument of this node and its children
215: */
216: void setOwnerDocument(CoreDocumentImpl doc) {
217: if (needsSyncChildren()) {
218: synchronizeChildren();
219: }
220: super .setOwnerDocument(doc);
221: ownerDocument = doc;
222: for (ChildNode child = firstChild; child != null; child = child.nextSibling) {
223: child.setOwnerDocument(doc);
224: }
225: }
226:
227: /**
228: * Test whether this node has any children. Convenience shorthand
229: * for (Node.getFirstChild()!=null)
230: */
231: public boolean hasChildNodes() {
232: if (needsSyncChildren()) {
233: synchronizeChildren();
234: }
235: return firstChild != null;
236: }
237:
238: /**
239: * Obtain a NodeList enumerating all children of this node. If there
240: * are none, an (initially) empty NodeList is returned.
241: * <p>
242: * NodeLists are "live"; as children are added/removed the NodeList
243: * will immediately reflect those changes. Also, the NodeList refers
244: * to the actual nodes, so changes to those nodes made via the DOM tree
245: * will be reflected in the NodeList and vice versa.
246: * <p>
247: * In this implementation, Nodes implement the NodeList interface and
248: * provide their own getChildNodes() support. Other DOMs may solve this
249: * differently.
250: */
251: public NodeList getChildNodes() {
252:
253: if (needsSyncChildren()) {
254: synchronizeChildren();
255: }
256: return this ;
257:
258: } // getChildNodes():NodeList
259:
260: /** The first child of this Node, or null if none. */
261: public Node getFirstChild() {
262:
263: if (needsSyncChildren()) {
264: synchronizeChildren();
265: }
266: return firstChild;
267:
268: } // getFirstChild():Node
269:
270: /** The last child of this Node, or null if none. */
271: public Node getLastChild() {
272:
273: if (needsSyncChildren()) {
274: synchronizeChildren();
275: }
276: return lastChild();
277:
278: } // getLastChild():Node
279:
280: final ChildNode lastChild() {
281: // last child is stored as the previous sibling of first child
282: return firstChild != null ? firstChild.previousSibling : null;
283: }
284:
285: final void lastChild(ChildNode node) {
286: // store lastChild as previous sibling of first child
287: if (firstChild != null) {
288: firstChild.previousSibling = node;
289: }
290: }
291:
292: /**
293: * Move one or more node(s) to our list of children. Note that this
294: * implicitly removes them from their previous parent.
295: *
296: * @param newChild The Node to be moved to our subtree. As a
297: * convenience feature, inserting a DocumentNode will instead insert
298: * all its children.
299: *
300: * @param refChild Current child which newChild should be placed
301: * immediately before. If refChild is null, the insertion occurs
302: * after all existing Nodes, like appendChild().
303: *
304: * @returns newChild, in its new state (relocated, or emptied in the
305: * case of DocumentNode.)
306: *
307: * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
308: * type that shouldn't be a child of this node, or if newChild is an
309: * ancestor of this node.
310: *
311: * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
312: * different owner document than we do.
313: *
314: * @throws DOMException(NOT_FOUND_ERR) if refChild is not a child of
315: * this node.
316: *
317: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
318: * read-only.
319: */
320: public Node insertBefore(Node newChild, Node refChild)
321: throws DOMException {
322: // Tail-call; optimizer should be able to do good things with.
323: return internalInsertBefore(newChild, refChild, false);
324: } // insertBefore(Node,Node):Node
325:
326: /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
327: * to control which mutation events are spawned. This version of the
328: * insertBefore operation allows us to do so. It is not intended
329: * for use by application programs.
330: */
331: Node internalInsertBefore(Node newChild, Node refChild,
332: boolean replace) throws DOMException {
333:
334: boolean errorChecking = ownerDocument.errorChecking;
335:
336: if (newChild.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {
337: // SLOW BUT SAFE: We could insert the whole subtree without
338: // juggling so many next/previous pointers. (Wipe out the
339: // parent's child-list, patch the parent pointers, set the
340: // ends of the list.) But we know some subclasses have special-
341: // case behavior they add to insertBefore(), so we don't risk it.
342: // This approch also takes fewer bytecodes.
343:
344: // NOTE: If one of the children is not a legal child of this
345: // node, throw HIERARCHY_REQUEST_ERR before _any_ of the children
346: // have been transferred. (Alternative behaviors would be to
347: // reparent up to the first failure point or reparent all those
348: // which are acceptable to the target node, neither of which is
349: // as robust. PR-DOM-0818 isn't entirely clear on which it
350: // recommends?????
351:
352: // No need to check kids for right-document; if they weren't,
353: // they wouldn't be kids of that DocFrag.
354: if (errorChecking) {
355: for (Node kid = newChild.getFirstChild(); // Prescan
356: kid != null; kid = kid.getNextSibling()) {
357:
358: if (!ownerDocument.isKidOK(this , kid)) {
359: throw new DOMException(
360: DOMException.HIERARCHY_REQUEST_ERR,
361: "DOM006 Hierarchy request error");
362: }
363: }
364: }
365:
366: while (newChild.hasChildNodes()) {
367: insertBefore(newChild.getFirstChild(), refChild);
368: }
369: return newChild;
370: }
371:
372: if (newChild == refChild) {
373: // stupid case that must be handled as a no-op triggering events...
374: refChild = refChild.getNextSibling();
375: removeChild(newChild);
376: insertBefore(newChild, refChild);
377: return newChild;
378: }
379:
380: if (needsSyncChildren()) {
381: synchronizeChildren();
382: }
383:
384: if (errorChecking) {
385: if (isReadOnly()) {
386: throw new DOMException(
387: DOMException.NO_MODIFICATION_ALLOWED_ERR,
388: "DOM001 Modification not allowed");
389: }
390: if (newChild.getOwnerDocument() != ownerDocument) {
391: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
392: "DOM005 Wrong document");
393: }
394: if (!ownerDocument.isKidOK(this , newChild)) {
395: throw new DOMException(
396: DOMException.HIERARCHY_REQUEST_ERR,
397: "DOM006 Hierarchy request error");
398: }
399: // refChild must be a child of this node (or null)
400: if (refChild != null && refChild.getParentNode() != this ) {
401: throw new DOMException(DOMException.NOT_FOUND_ERR,
402: "DOM008 Not found");
403: }
404:
405: // Prevent cycles in the tree
406: // newChild cannot be ancestor of this Node,
407: // and actually cannot be this
408: boolean treeSafe = true;
409: for (NodeImpl a = this ; treeSafe && a != null; a = a
410: .parentNode()) {
411: treeSafe = newChild != a;
412: }
413: if (!treeSafe) {
414: throw new DOMException(
415: DOMException.HIERARCHY_REQUEST_ERR,
416: "DOM006 Hierarchy request error");
417: }
418: }
419:
420: // notify document
421: ownerDocument.insertingNode(this , replace);
422:
423: // Convert to internal type, to avoid repeated casting
424: ChildNode newInternal = (ChildNode) newChild;
425:
426: Node oldparent = newInternal.parentNode();
427: if (oldparent != null) {
428: oldparent.removeChild(newInternal);
429: }
430:
431: // Convert to internal type, to avoid repeated casting
432: ChildNode refInternal = (ChildNode) refChild;
433:
434: // Attach up
435: newInternal.ownerNode = this ;
436: newInternal.isOwned(true);
437:
438: // Attach before and after
439: // Note: firstChild.previousSibling == lastChild!!
440: if (firstChild == null) {
441: // this our first and only child
442: firstChild = newInternal;
443: newInternal.isFirstChild(true);
444: newInternal.previousSibling = newInternal;
445: } else {
446: if (refInternal == null) {
447: // this is an append
448: ChildNode lastChild = firstChild.previousSibling;
449: lastChild.nextSibling = newInternal;
450: newInternal.previousSibling = lastChild;
451: firstChild.previousSibling = newInternal;
452: } else {
453: // this is an insert
454: if (refChild == firstChild) {
455: // at the head of the list
456: firstChild.isFirstChild(false);
457: newInternal.nextSibling = firstChild;
458: newInternal.previousSibling = firstChild.previousSibling;
459: firstChild.previousSibling = newInternal;
460: firstChild = newInternal;
461: newInternal.isFirstChild(true);
462: } else {
463: // somewhere in the middle
464: ChildNode prev = refInternal.previousSibling;
465: newInternal.nextSibling = refInternal;
466: prev.nextSibling = newInternal;
467: refInternal.previousSibling = newInternal;
468: newInternal.previousSibling = prev;
469: }
470: }
471: }
472:
473: changed();
474:
475: // update cached length if we have any
476: if (fCachedLength != -1) {
477: fCachedLength++;
478: }
479: if (fCachedChildIndex != -1) {
480: // if we happen to insert just before the cached node, update
481: // the cache to the new node to match the cached index
482: if (fCachedChild == refInternal) {
483: fCachedChild = newInternal;
484: } else {
485: // otherwise just invalidate the cache
486: fCachedChildIndex = -1;
487: }
488: }
489:
490: // notify document
491: ownerDocument.insertedNode(this , newInternal, replace);
492:
493: checkNormalizationAfterInsert(newInternal);
494:
495: return newChild;
496:
497: } // internalInsertBefore(Node,Node,boolean):Node
498:
499: /**
500: * Remove a child from this Node. The removed child's subtree
501: * remains intact so it may be re-inserted elsewhere.
502: *
503: * @return oldChild, in its new state (removed).
504: *
505: * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
506: * this node.
507: *
508: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
509: * read-only.
510: */
511: public Node removeChild(Node oldChild) throws DOMException {
512: // Tail-call, should be optimizable
513: return internalRemoveChild(oldChild, false);
514: } // removeChild(Node) :Node
515:
516: /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
517: * to control which mutation events are spawned. This version of the
518: * removeChild operation allows us to do so. It is not intended
519: * for use by application programs.
520: */
521: Node internalRemoveChild(Node oldChild, boolean replace)
522: throws DOMException {
523:
524: CoreDocumentImpl ownerDocument = ownerDocument();
525: if (ownerDocument.errorChecking) {
526: if (isReadOnly()) {
527: throw new DOMException(
528: DOMException.NO_MODIFICATION_ALLOWED_ERR,
529: "DOM001 Modification not allowed");
530: }
531: if (oldChild != null && oldChild.getParentNode() != this ) {
532: throw new DOMException(DOMException.NOT_FOUND_ERR,
533: "DOM008 Not found");
534: }
535: }
536:
537: ChildNode oldInternal = (ChildNode) oldChild;
538:
539: // notify document
540: ownerDocument.removingNode(this , oldInternal, replace);
541:
542: // update cached length if we have any
543: if (fCachedLength != -1) {
544: fCachedLength--;
545: }
546: if (fCachedChildIndex != -1) {
547: // if the removed node is the cached node
548: // move the cache to its (soon former) previous sibling
549: if (fCachedChild == oldInternal) {
550: fCachedChildIndex--;
551: fCachedChild = oldInternal.previousSibling();
552: } else {
553: // otherwise just invalidate the cache
554: fCachedChildIndex = -1;
555: }
556: }
557:
558: // Patch linked list around oldChild
559: // Note: lastChild == firstChild.previousSibling
560: if (oldInternal == firstChild) {
561: // removing first child
562: oldInternal.isFirstChild(false);
563: firstChild = oldInternal.nextSibling;
564: if (firstChild != null) {
565: firstChild.isFirstChild(true);
566: firstChild.previousSibling = oldInternal.previousSibling;
567: }
568: } else {
569: ChildNode prev = oldInternal.previousSibling;
570: ChildNode next = oldInternal.nextSibling;
571: prev.nextSibling = next;
572: if (next == null) {
573: // removing last child
574: firstChild.previousSibling = prev;
575: } else {
576: // removing some other child in the middle
577: next.previousSibling = prev;
578: }
579: }
580:
581: // Save previous sibling for normalization checking.
582: ChildNode oldPreviousSibling = oldInternal.previousSibling();
583:
584: // Remove oldInternal's references to tree
585: oldInternal.ownerNode = ownerDocument;
586: oldInternal.isOwned(false);
587: oldInternal.nextSibling = null;
588: oldInternal.previousSibling = null;
589:
590: changed();
591:
592: // notify document
593: ownerDocument.removedNode(this , replace);
594:
595: checkNormalizationAfterRemove(oldPreviousSibling);
596:
597: return oldInternal;
598:
599: } // internalRemoveChild(Node,boolean):Node
600:
601: /**
602: * Make newChild occupy the location that oldChild used to
603: * have. Note that newChild will first be removed from its previous
604: * parent, if any. Equivalent to inserting newChild before oldChild,
605: * then removing oldChild.
606: *
607: * @returns oldChild, in its new state (removed).
608: *
609: * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
610: * type that shouldn't be a child of this node, or if newChild is
611: * one of our ancestors.
612: *
613: * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
614: * different owner document than we do.
615: *
616: * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
617: * this node.
618: *
619: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
620: * read-only.
621: */
622: public Node replaceChild(Node newChild, Node oldChild)
623: throws DOMException {
624: // If Mutation Events are being generated, this operation might
625: // throw aggregate events twice when modifying an Attr -- once
626: // on insertion and once on removal. DOM Level 2 does not specify
627: // this as either desirable or undesirable, but hints that
628: // aggregations should be issued only once per user request.
629:
630: // notify document
631: ownerDocument.replacingNode(this );
632:
633: internalInsertBefore(newChild, oldChild, true);
634: if (newChild != oldChild) {
635: internalRemoveChild(oldChild, true);
636: }
637:
638: // notify document
639: ownerDocument.replacedNode(this );
640:
641: return oldChild;
642: }
643:
644: //
645: // NodeList methods
646: //
647:
648: /**
649: * Count the immediate children of this node. Use to implement
650: * NodeList.getLength().
651: * @return int
652: */
653: private int nodeListGetLength() {
654:
655: if (fCachedLength == -1) { // is the cached length invalid ?
656: ChildNode node;
657: // start from the cached node if we have one
658: if (fCachedChildIndex != -1 && fCachedChild != null) {
659: fCachedLength = fCachedChildIndex;
660: node = fCachedChild;
661: } else {
662: node = firstChild;
663: fCachedLength = 0;
664: }
665: for (; node != null; node = node.nextSibling) {
666: fCachedLength++;
667: }
668: }
669:
670: return fCachedLength;
671:
672: } // nodeListGetLength():int
673:
674: /**
675: * NodeList method: Count the immediate children of this node
676: * @return int
677: */
678: public int getLength() {
679: return nodeListGetLength();
680: }
681:
682: /**
683: * Return the Nth immediate child of this node, or null if the index is
684: * out of bounds. Use to implement NodeList.item().
685: * @param index int
686: */
687: private Node nodeListItem(int index) {
688: // short way
689: if (fCachedChildIndex != -1 && fCachedChild != null) {
690: if (fCachedChildIndex < index) {
691: while (fCachedChildIndex < index
692: && fCachedChild != null) {
693: fCachedChildIndex++;
694: fCachedChild = fCachedChild.nextSibling;
695: }
696: } else if (fCachedChildIndex > index) {
697: while (fCachedChildIndex > index
698: && fCachedChild != null) {
699: fCachedChildIndex--;
700: fCachedChild = fCachedChild.previousSibling();
701: }
702: }
703: return fCachedChild;
704: }
705:
706: // long way
707: fCachedChild = firstChild;
708: for (fCachedChildIndex = 0; fCachedChildIndex < index
709: && fCachedChild != null; fCachedChildIndex++) {
710: fCachedChild = fCachedChild.nextSibling;
711: }
712: return fCachedChild;
713:
714: } // nodeListItem(int):Node
715:
716: /**
717: * NodeList method: Return the Nth immediate child of this node, or
718: * null if the index is out of bounds.
719: * @return org.w3c.dom.Node
720: * @param index int
721: */
722: public Node item(int index) {
723: return nodeListItem(index);
724: } // item(int):Node
725:
726: /**
727: * Create a NodeList to access children that is use by subclass elements
728: * that have methods named getLength() or item(int). ChildAndParentNode
729: * optimizes getChildNodes() by implementing NodeList itself. However if
730: * a subclass Element implements methods with the same name as the NodeList
731: * methods, they will override the actually methods in this class.
732: * <p>
733: * To use this method, the subclass should implement getChildNodes() and
734: * have it call this method. The resulting NodeList instance maybe
735: * shared and cached in a transient field, but the cached value must be
736: * cleared if the node is cloned.
737: */
738: protected final NodeList getChildNodesUnoptimized() {
739: if (needsSyncChildren()) {
740: synchronizeChildren();
741: }
742: return new NodeList() {
743: /**
744: * @see NodeList.getLength()
745: */
746: public int getLength() {
747: return nodeListGetLength();
748: } // getLength():int
749:
750: /**
751: * @see NodeList.item(int)
752: */
753: public Node item(int index) {
754: return nodeListItem(index);
755: } // item(int):Node
756: };
757: } // getChildNodesUnoptimized():NodeList
758:
759: //
760: // DOM2: methods, getters, setters
761: //
762:
763: /**
764: * Override default behavior to call normalize() on this Node's
765: * children. It is up to implementors or Node to override normalize()
766: * to take action.
767: */
768: public void normalize() {
769: // No need to normalize if already normalized.
770: if (isNormalized()) {
771: return;
772: }
773: if (needsSyncChildren()) {
774: synchronizeChildren();
775: }
776: ChildNode kid;
777: for (kid = firstChild; kid != null; kid = kid.nextSibling) {
778: kid.normalize();
779: }
780: isNormalized(true);
781: }
782:
783: //
784: // Public methods
785: //
786:
787: /**
788: * Override default behavior so that if deep is true, children are also
789: * toggled.
790: * @see Node
791: * <P>
792: * Note: this will not change the state of an EntityReference or its
793: * children, which are always read-only.
794: */
795: public void setReadOnly(boolean readOnly, boolean deep) {
796:
797: super .setReadOnly(readOnly, deep);
798:
799: if (deep) {
800:
801: if (needsSyncChildren()) {
802: synchronizeChildren();
803: }
804:
805: // Recursively set kids
806: for (ChildNode mykid = firstChild; mykid != null; mykid = mykid.nextSibling) {
807: if (mykid.getNodeType() != Node.ENTITY_REFERENCE_NODE) {
808: mykid.setReadOnly(readOnly, true);
809: }
810: }
811: }
812: } // setReadOnly(boolean,boolean)
813:
814: //
815: // Protected methods
816: //
817:
818: /**
819: * Override this method in subclass to hook in efficient
820: * internal data structure.
821: */
822: protected void synchronizeChildren() {
823: // By default just change the flag to avoid calling this method again
824: needsSyncChildren(false);
825: }
826:
827: /**
828: * Checks the normalized state of this node after inserting a child.
829: * If the inserted child causes this node to be unnormalized, then this
830: * node is flagged accordingly.
831: * The conditions for changing the normalized state are:
832: * <ul>
833: * <li>The inserted child is a text node and one of its adjacent siblings
834: * is also a text node.
835: * <li>The inserted child is is itself unnormalized.
836: * </ul>
837: *
838: * @param insertedChild the child node that was inserted into this node
839: *
840: * @throws NullPointerException if the inserted child is <code>null</code>
841: */
842: void checkNormalizationAfterInsert(ChildNode insertedChild) {
843: // See if insertion caused this node to be unnormalized.
844: if (insertedChild.getNodeType() == Node.TEXT_NODE) {
845: ChildNode prev = insertedChild.previousSibling();
846: ChildNode next = insertedChild.nextSibling;
847: // If an adjacent sibling of the new child is a text node,
848: // flag this node as unnormalized.
849: if ((prev != null && prev.getNodeType() == Node.TEXT_NODE)
850: || (next != null && next.getNodeType() == Node.TEXT_NODE)) {
851: isNormalized(false);
852: }
853: } else {
854: // If the new child is not normalized,
855: // then this node is inherently not normalized.
856: if (!insertedChild.isNormalized()) {
857: isNormalized(false);
858: }
859: }
860: } // checkNormalizationAfterInsert(ChildNode)
861:
862: /**
863: * Checks the normalized of this node after removing a child.
864: * If the removed child causes this node to be unnormalized, then this
865: * node is flagged accordingly.
866: * The conditions for changing the normalized state are:
867: * <ul>
868: * <li>The removed child had two adjacent siblings that were text nodes.
869: * </ul>
870: *
871: * @param previousSibling the previous sibling of the removed child, or
872: * <code>null</code>
873: */
874: void checkNormalizationAfterRemove(ChildNode previousSibling) {
875: // See if removal caused this node to be unnormalized.
876: // If the adjacent siblings of the removed child were both text nodes,
877: // flag this node as unnormalized.
878: if (previousSibling != null
879: && previousSibling.getNodeType() == Node.TEXT_NODE) {
880:
881: ChildNode next = previousSibling.nextSibling;
882: if (next != null && next.getNodeType() == Node.TEXT_NODE) {
883: isNormalized(false);
884: }
885: }
886: } // checkNormalizationAfterRemove(Node)
887:
888: //
889: // Serialization methods
890: //
891:
892: /** Serialize object. */
893: private void writeObject(ObjectOutputStream out) throws IOException {
894:
895: // synchronize chilren
896: if (needsSyncChildren()) {
897: synchronizeChildren();
898: }
899: // write object
900: out.defaultWriteObject();
901:
902: } // writeObject(ObjectOutputStream)
903:
904: /** Deserialize object. */
905: private void readObject(ObjectInputStream ois)
906: throws ClassNotFoundException, IOException {
907:
908: // perform default deseralization
909: ois.defaultReadObject();
910:
911: // hardset synchildren - so we don't try to sync- it does not make any sense
912: // to try to synchildren when we just desealize object.
913:
914: needsSyncChildren(false);
915:
916: // initialize transients
917: fCachedLength = -1;
918: fCachedChildIndex = -1;
919:
920: } // readObject(ObjectInputStream)
921:
922: } // class ParentNode
|