1 /*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5 /*
6 * Copyright 1999-2004 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.IOException;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectOutputStream;
26
27 import org.w3c.dom.TypeInfo;
28 import org.w3c.dom.Attr;
29 import org.w3c.dom.DOMException;
30 import org.w3c.dom.Element;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33 import org.w3c.dom.Text;
34
35 /**
36 * Attribute represents an XML-style attribute of an
37 * Element. Typically, the allowable values are controlled by its
38 * declaration in the Document Type Definition (DTD) governing this
39 * kind of document.
40 * <P>
41 * If the attribute has not been explicitly assigned a value, but has
42 * been declared in the DTD, it will exist and have that default. Only
43 * if neither the document nor the DTD specifies a value will the
44 * Attribute really be considered absent and have no value; in that
45 * case, querying the attribute will return null.
46 * <P>
47 * Attributes may have multiple children that contain their data. (XML
48 * allows attributes to contain entity references, and tokenized
49 * attribute types such as NMTOKENS may have a child for each token.)
50 * For convenience, the Attribute object's getValue() method returns
51 * the string version of the attribute's value.
52 * <P>
53 * Attributes are not children of the Elements they belong to, in the
54 * usual sense, and have no valid Parent reference. However, the spec
55 * says they _do_ belong to a specific Element, and an INUSE exception
56 * is to be thrown if the user attempts to explicitly share them
57 * between elements.
58 * <P>
59 * Note that Elements do not permit attributes to appear to be shared
60 * (see the INUSE exception), so this object's mutability is
61 * officially not an issue.
62 * <p>
63 * Note: The ownerNode attribute is used to store the Element the Attr
64 * node is associated with. Attr nodes do not have parent nodes.
65 * Besides, the getOwnerElement() method can be used to get the element node
66 * this attribute is associated with.
67 * <P>
68 * AttrImpl does not support Namespaces. AttrNSImpl, which inherits from
69 * it, does.
70 *
71 * <p>AttrImpl used to inherit from ParentNode. It now directly inherits from
72 * NodeImpl and provide its own implementation of the ParentNode's behavior.
73 * The reason is that we now try and avoid to always create a Text node to
74 * hold the value of an attribute. The DOM spec requires it, so we still have
75 * to do it in case getFirstChild() is called for instance. The reason
76 * attribute values are stored as a list of nodes is so that they can carry
77 * more than a simple string. They can also contain EntityReference nodes.
78 * However, most of the times people only have a single string that they only
79 * set and get through Element.set/getAttribute or Attr.set/getValue. In this
80 * new version, the Attr node has a value pointer which can either be the
81 * String directly or a pointer to the first ChildNode. A flag tells which one
82 * it currently is. Note that while we try to stick with the direct String as
83 * much as possible once we've switched to a node there is no going back. This
84 * is because we have no way to know whether the application keeps referring to
85 * the node we once returned.
86 * <p> The gain in memory varies on the density of attributes in the document.
87 * But in the tests I've run I've seen up to 12% of memory gain. And the good
88 * thing is that it also leads to a slight gain in speed because we allocate
89 * fewer objects! I mean, that's until we have to actually create the node...
90 * <p>
91 * To avoid too much duplicated code, I got rid of ParentNode and renamed
92 * ChildAndParentNode, which I never really liked, to ParentNode for
93 * simplicity, this doesn't make much of a difference in memory usage because
94 * there are only very few objects that are only a Parent. This is only true
95 * now because AttrImpl now inherits directly from NodeImpl and has its own
96 * implementation of the ParentNode's node behavior. So there is still some
97 * duplicated code there.
98 * <p>
99 * This class doesn't directly support mutation events, however, it notifies
100 * the document when mutations are performed so that the document class do so.
101 *
102 * <p><b>WARNING</b>: Some of the code here is partially duplicated in
103 * ParentNode, be careful to keep these two classes in sync!
104 *
105 * @xerces.internal
106 *
107 * @see AttrNSImpl
108 *
109 * @author Arnaud Le Hors, IBM
110 * @author Joe Kesselman, IBM
111 * @author Andy Clark, IBM
112 * @since PR-DOM-Level-1-19980818.
113 *
114 */
115 public class AttrImpl
116 extends NodeImpl
117 implements Attr, TypeInfo{
118
119 //
120 // Constants
121 //
122
123 /** Serialization version. */
124 static final long serialVersionUID = 7277707688218972102L;
125
126 /** DTD namespace. **/
127 static final String DTD_URI = "http://www.w3.org/TR/REC-xml";
128
129 //
130 // Data
131 //
132
133 /** This can either be a String or the first child node. */
134 protected Object value = null;
135
136 /** Attribute name. */
137 protected String name;
138
139 /** Type information */
140 // REVISIT: we are losing the type information in DOM during serialization
141 transient Object type;
142
143 protected static TextImpl textNode = null;
144
145 //
146 // Constructors
147 //
148
149 /**
150 * Attribute has no public constructor. Please use the factory
151 * method in the Document class.
152 */
153 protected AttrImpl(CoreDocumentImpl ownerDocument, String name) {
154 super(ownerDocument);
155 this.name = name;
156 /** False for default attributes. */
157 isSpecified(true);
158 hasStringValue(true);
159 }
160
161 // for AttrNSImpl
162 protected AttrImpl() {}
163
164 // Support for DOM Level 3 renameNode method.
165 // Note: This only deals with part of the pb. It is expected to be
166 // called after the Attr has been detached for one thing.
167 // CoreDocumentImpl does all the work.
168 void rename(String name) {
169 if (needsSyncData()) {
170 synchronizeData();
171 }
172 this.name = name;
173 }
174
175 // create a real text node as child if we don't have one yet
176 protected void makeChildNode() {
177 if (hasStringValue()) {
178 if (value != null) {
179 TextImpl text =
180 (TextImpl) ownerDocument().createTextNode((String) value);
181 value = text;
182 text.isFirstChild(true);
183 text.previousSibling = text;
184 text.ownerNode = this;
185 text.isOwned(true);
186 }
187 hasStringValue(false);
188 }
189 }
190
191 /**
192 * NON-DOM
193 * set the ownerDocument of this node and its children
194 */
195 void setOwnerDocument(CoreDocumentImpl doc) {
196 if (needsSyncChildren()) {
197 synchronizeChildren();
198 }
199 super.setOwnerDocument(doc);
200 if (!hasStringValue()) {
201 for (ChildNode child = (ChildNode) value;
202 child != null; child = child.nextSibling) {
203 child.setOwnerDocument(doc);
204 }
205 }
206 }
207
208 /**
209 * NON-DOM: set the type of this attribute to be ID type.
210 *
211 * @param id
212 */
213 public void setIdAttribute(boolean id){
214 if (needsSyncData()) {
215 synchronizeData();
216 }
217 isIdAttribute(id);
218 }
219 /** DOM Level 3: isId*/
220 public boolean isId(){
221 // REVISIT: should an attribute that is not in the tree return
222 // isID true?
223 return isIdAttribute();
224 }
225
226
227 //
228 // Node methods
229 //
230
231 public Node cloneNode(boolean deep) {
232
233 if (needsSyncChildren()) {
234 synchronizeChildren();
235 }
236 AttrImpl clone = (AttrImpl) super.cloneNode(deep);
237
238 // take care of case where there are kids
239 if (!clone.hasStringValue()) {
240
241 // Need to break the association w/ original kids
242 clone.value = null;
243
244 // Cloning an Attribute always clones its children,
245 // since they represent its value, no matter whether this
246 // is a deep clone or not
247 for (Node child = (Node) value; child != null;
248 child = child.getNextSibling()) {
249 clone.appendChild(child.cloneNode(true));
250 }
251 }
252 clone.isSpecified(true);
253 return clone;
254 }
255
256 /**
257 * A short integer indicating what type of node this is. The named
258 * constants for this value are defined in the org.w3c.dom.Node interface.
259 */
260 public short getNodeType() {
261 return Node.ATTRIBUTE_NODE;
262 }
263
264 /**
265 * Returns the attribute name
266 */
267 public String getNodeName() {
268 if (needsSyncData()) {
269 synchronizeData();
270 }
271 return name;
272 }
273
274 /**
275 * Implicit in the rerouting of getNodeValue to getValue is the
276 * need to redefine setNodeValue, for symmetry's sake. Note that
277 * since we're explicitly providing a value, Specified should be set
278 * true.... even if that value equals the default.
279 */
280 public void setNodeValue(String value) throws DOMException {
281 setValue(value);
282 }
283
284 /**
285 * @see org.w3c.dom.TypeInfo#getTypeName()
286 */
287 public String getTypeName() {
288 return (String)type;
289 }
290
291 /**
292 * @see org.w3c.dom.TypeInfo#getTypeNamespace()
293 */
294 public String getTypeNamespace() {
295 if (type != null) {
296 return DTD_URI;
297 }
298 return null;
299 }
300
301 /**
302 * Method getSchemaTypeInfo.
303 * @return TypeInfo
304 */
305 public TypeInfo getSchemaTypeInfo(){
306 return this;
307 }
308
309 /**
310 * In Attribute objects, NodeValue is considered a synonym for
311 * Value.
312 *
313 * @see #getValue()
314 */
315 public String getNodeValue() {
316 return getValue();
317 }
318
319 //
320 // Attr methods
321 //
322
323 /**
324 * In Attributes, NodeName is considered a synonym for the
325 * attribute's Name
326 */
327 public String getName() {
328
329 if (needsSyncData()) {
330 synchronizeData();
331 }
332 return name;
333
334 } // getName():String
335
336 /**
337 * The DOM doesn't clearly define what setValue(null) means. I've taken it
338 * as "remove all children", which from outside should appear
339 * similar to setting it to the empty string.
340 */
341 public void setValue(String newvalue) {
342
343 CoreDocumentImpl ownerDocument = ownerDocument();
344
345 if (ownerDocument.errorChecking && isReadOnly()) {
346 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
347 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
348 }
349
350 Element ownerElement = getOwnerElement();
351 String oldvalue = "";
352 if (needsSyncData()) {
353 synchronizeData();
354 }
355 if (needsSyncChildren()) {
356 synchronizeChildren();
357 }
358 if (value != null) {
359 if (ownerDocument.getMutationEvents()) {
360 // Can no longer just discard the kids; they may have
361 // event listeners waiting for them to disconnect.
362 if (hasStringValue()) {
363 oldvalue = (String) value;
364 // create an actual text node as our child so
365 // that we can use it in the event
366 if (textNode == null) {
367 textNode = (TextImpl)
368 ownerDocument.createTextNode((String) value);
369 }
370 else {
371 textNode.data = (String) value;
372 }
373 value = textNode;
374 textNode.isFirstChild(true);
375 textNode.previousSibling = textNode;
376 textNode.ownerNode = this;
377 textNode.isOwned(true);
378 hasStringValue(false);
379 internalRemoveChild(textNode, true);
380 }
381 else {
382 oldvalue = getValue();
383 while (value != null) {
384 internalRemoveChild((Node) value, true);
385 }
386 }
387 }
388 else {
389 if (hasStringValue()) {
390 oldvalue = (String) value;
391 }
392 else {
393 // simply discard children if any
394 oldvalue = getValue();
395 // remove ref from first child to last child
396 ChildNode firstChild = (ChildNode) value;
397 firstChild.previousSibling = null;
398 firstChild.isFirstChild(false);
399 firstChild.ownerNode = ownerDocument;
400 }
401 // then remove ref to current value
402 value = null;
403 needsSyncChildren(false);
404 }
405 if (isIdAttribute() && ownerElement != null) {
406 ownerDocument.removeIdentifier(oldvalue);
407 }
408 }
409
410 // Create and add the new one, generating only non-aggregate events
411 // (There are no listeners on the new Text, but there may be
412 // capture/bubble listeners on the Attr.
413 // Note that aggregate events are NOT dispatched here,
414 // since we need to combine the remove and insert.
415 isSpecified(true);
416 if (ownerDocument.getMutationEvents()) {
417 // if there are any event handlers create a real node
418 internalInsertBefore(ownerDocument.createTextNode(newvalue),
419 null, true);
420 hasStringValue(false);
421 // notify document
422 ownerDocument.modifiedAttrValue(this, oldvalue);
423 } else {
424 // directly store the string
425 value = newvalue;
426 hasStringValue(true);
427 changed();
428 }
429 if (isIdAttribute() && ownerElement != null) {
430 ownerDocument.putIdentifier(newvalue, ownerElement);
431 }
432
433 } // setValue(String)
434
435 /**
436 * The "string value" of an Attribute is its text representation,
437 * which in turn is a concatenation of the string values of its children.
438 */
439 public String getValue() {
440
441 if (needsSyncData()) {
442 synchronizeData();
443 }
444 if (needsSyncChildren()) {
445 synchronizeChildren();
446 }
447 if (value == null) {
448 return "";
449 }
450 if (hasStringValue()) {
451 return (String) value;
452 }
453
454 ChildNode firstChild = ((ChildNode) value);
455
456 String data = null;
457 if (firstChild.getNodeType() == Node.ENTITY_REFERENCE_NODE){
458 data = ((EntityReferenceImpl)firstChild).getEntityRefValue();
459 }
460 else {
461 data = firstChild.getNodeValue();
462 }
463
464 ChildNode node = firstChild.nextSibling;
465
466 if (node == null || data == null) return (data == null)?"":data;
467
468 StringBuffer value = new StringBuffer(data);
469 while (node != null) {
470 if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE){
471 data = ((EntityReferenceImpl)node).getEntityRefValue();
472 if (data == null) return "";
473 value.append(data);
474 }
475 else {
476 value.append(node.getNodeValue());
477 }
478 node = node.nextSibling;
479 }
480 return value.toString();
481
482 } // getValue():String
483
484
485 /**
486 * The "specified" flag is true if and only if this attribute's
487 * value was explicitly specified in the original document. Note that
488 * the implementation, not the user, is in charge of this
489 * property. If the user asserts an Attribute value (even if it ends
490 * up having the same value as the default), it is considered a
491 * specified attribute. If you really want to revert to the default,
492 * delete the attribute from the Element, and the Implementation will
493 * re-assert the default (if any) in its place, with the appropriate
494 * specified=false setting.
495 */
496 public boolean getSpecified() {
497
498 if (needsSyncData()) {
499 synchronizeData();
500 }
501 return isSpecified();
502
503 } // getSpecified():boolean
504
505 //
506 // Attr2 methods
507 //
508
509 /**
510 * Returns the element node that this attribute is associated with,
511 * or null if the attribute has not been added to an element.
512 *
513 * @see #getOwnerElement
514 *
515 * @deprecated Previous working draft of DOM Level 2. New method
516 * is <tt>getOwnerElement()</tt>.
517 */
518 public Element getElement() {
519 // if we have an owner, ownerNode is our ownerElement, otherwise it's
520 // our ownerDocument and we don't have an ownerElement
521 return (Element) (isOwned() ? ownerNode : null);
522 }
523
524 /**
525 * Returns the element node that this attribute is associated with,
526 * or null if the attribute has not been added to an element.
527 *
528 * @since WD-DOM-Level-2-19990719
529 */
530 public Element getOwnerElement() {
531 // if we have an owner, ownerNode is our ownerElement, otherwise it's
532 // our ownerDocument and we don't have an ownerElement
533 return (Element) (isOwned() ? ownerNode : null);
534 }
535
536 public void normalize() {
537
538 // No need to normalize if already normalized or
539 // if value is kept as a String.
540 if (isNormalized() || hasStringValue())
541 return;
542
543 Node kid, next;
544 ChildNode firstChild = (ChildNode)value;
545 for (kid = firstChild; kid != null; kid = next) {
546 next = kid.getNextSibling();
547
548 // If kid is a text node, we need to check for one of two
549 // conditions:
550 // 1) There is an adjacent text node
551 // 2) There is no adjacent text node, but kid is
552 // an empty text node.
553 if ( kid.getNodeType() == Node.TEXT_NODE )
554 {
555 // If an adjacent text node, merge it with kid
556 if ( next!=null && next.getNodeType() == Node.TEXT_NODE )
557 {
558 ((Text)kid).appendData(next.getNodeValue());
559 removeChild( next );
560 next = kid; // Don't advance; there might be another.
561 }
562 else
563 {
564 // If kid is empty, remove it
565 if ( kid.getNodeValue() == null || kid.getNodeValue().length() == 0 ) {
566 removeChild( kid );
567 }
568 }
569 }
570 }
571
572 isNormalized(true);
573 } // normalize()
574
575 //
576 // Public methods
577 //
578
579 /** NON-DOM, for use by parser */
580 public void setSpecified(boolean arg) {
581
582 if (needsSyncData()) {
583 synchronizeData();
584 }
585 isSpecified(arg);
586
587 } // setSpecified(boolean)
588
589 /**
590 * NON-DOM: used by the parser
591 * @param type
592 */
593 public void setType (Object type){
594 this.type = type;
595 }
596
597 //
598 // Object methods
599 //
600
601 /** NON-DOM method for debugging convenience */
602 public String toString() {
603 return getName() + "=" + "\"" + getValue() + "\"";
604 }
605
606 /**
607 * Test whether this node has any children. Convenience shorthand
608 * for (Node.getFirstChild()!=null)
609 */
610 public boolean hasChildNodes() {
611 if (needsSyncChildren()) {
612 synchronizeChildren();
613 }
614 return value != null;
615 }
616
617 /**
618 * Obtain a NodeList enumerating all children of this node. If there
619 * are none, an (initially) empty NodeList is returned.
620 * <p>
621 * NodeLists are "live"; as children are added/removed the NodeList
622 * will immediately reflect those changes. Also, the NodeList refers
623 * to the actual nodes, so changes to those nodes made via the DOM tree
624 * will be reflected in the NodeList and vice versa.
625 * <p>
626 * In this implementation, Nodes implement the NodeList interface and
627 * provide their own getChildNodes() support. Other DOMs may solve this
628 * differently.
629 */
630 public NodeList getChildNodes() {
631 // JKESS: KNOWN ISSUE HERE
632
633 if (needsSyncChildren()) {
634 synchronizeChildren();
635 }
636 return this;
637
638 } // getChildNodes():NodeList
639
640 /** The first child of this Node, or null if none. */
641 public Node getFirstChild() {
642
643 if (needsSyncChildren()) {
644 synchronizeChildren();
645 }
646 makeChildNode();
647 return (Node) value;
648
649 } // getFirstChild():Node
650
651 /** The last child of this Node, or null if none. */
652 public Node getLastChild() {
653
654 if (needsSyncChildren()) {
655 synchronizeChildren();
656 }
657 return lastChild();
658
659 } // getLastChild():Node
660
661 final ChildNode lastChild() {
662 // last child is stored as the previous sibling of first child
663 makeChildNode();
664 return value != null ? ((ChildNode) value).previousSibling : null;
665 }
666
667 final void lastChild(ChildNode node) {
668 // store lastChild as previous sibling of first child
669 if (value != null) {
670 ((ChildNode) value).previousSibling = node;
671 }
672 }
673
674 /**
675 * Move one or more node(s) to our list of children. Note that this
676 * implicitly removes them from their previous parent.
677 *
678 * @param newChild The Node to be moved to our subtree. As a
679 * convenience feature, inserting a DocumentNode will instead insert
680 * all its children.
681 *
682 * @param refChild Current child which newChild should be placed
683 * immediately before. If refChild is null, the insertion occurs
684 * after all existing Nodes, like appendChild().
685 *
686 * @return newChild, in its new state (relocated, or emptied in the case of
687 * DocumentNode.)
688 *
689 * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
690 * type that shouldn't be a child of this node, or if newChild is an
691 * ancestor of this node.
692 *
693 * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
694 * different owner document than we do.
695 *
696 * @throws DOMException(NOT_FOUND_ERR) if refChild is not a child of
697 * this node.
698 *
699 * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
700 * read-only.
701 */
702 public Node insertBefore(Node newChild, Node refChild)
703 throws DOMException {
704 // Tail-call; optimizer should be able to do good things with.
705 return internalInsertBefore(newChild, refChild, false);
706 } // insertBefore(Node,Node):Node
707
708 /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
709 * to control which mutation events are spawned. This version of the
710 * insertBefore operation allows us to do so. It is not intended
711 * for use by application programs.
712 */
713 Node internalInsertBefore(Node newChild, Node refChild, boolean replace)
714 throws DOMException {
715
716 CoreDocumentImpl ownerDocument = ownerDocument();
717 boolean errorChecking = ownerDocument.errorChecking;
718
719 if (newChild.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {
720 // SLOW BUT SAFE: We could insert the whole subtree without
721 // juggling so many next/previous pointers. (Wipe out the
722 // parent's child-list, patch the parent pointers, set the
723 // ends of the list.) But we know some subclasses have special-
724 // case behavior they add to insertBefore(), so we don't risk it.
725 // This approch also takes fewer bytecodes.
726
727 // NOTE: If one of the children is not a legal child of this
728 // node, throw HIERARCHY_REQUEST_ERR before _any_ of the children
729 // have been transferred. (Alternative behaviors would be to
730 // reparent up to the first failure point or reparent all those
731 // which are acceptable to the target node, neither of which is
732 // as robust. PR-DOM-0818 isn't entirely clear on which it
733 // recommends?????
734
735 // No need to check kids for right-document; if they weren't,
736 // they wouldn't be kids of that DocFrag.
737 if (errorChecking) {
738 for (Node kid = newChild.getFirstChild(); // Prescan
739 kid != null; kid = kid.getNextSibling()) {
740
741 if (!ownerDocument.isKidOK(this, kid)) {
742 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
743 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
744 }
745 }
746 }
747
748 while (newChild.hasChildNodes()) {
749 insertBefore(newChild.getFirstChild(), refChild);
750 }
751 return newChild;
752 }
753
754 if (newChild == refChild) {
755 // stupid case that must be handled as a no-op triggering events...
756 refChild = refChild.getNextSibling();
757 removeChild(newChild);
758 insertBefore(newChild, refChild);
759 return newChild;
760 }
761
762 if (needsSyncChildren()) {
763 synchronizeChildren();
764 }
765
766 if (errorChecking) {
767 if (isReadOnly()) {
768 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
769 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
770 }
771 if (newChild.getOwnerDocument() != ownerDocument) {
772 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
773 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
774 }
775 if (!ownerDocument.isKidOK(this, newChild)) {
776 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
777 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
778 }
779 // refChild must be a child of this node (or null)
780 if (refChild != null && refChild.getParentNode() != this) {
781 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
782 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
783 }
784
785 // Prevent cycles in the tree
786 // newChild cannot be ancestor of this Node,
787 // and actually cannot be this
788 boolean treeSafe = true;
789 for (NodeImpl a = this; treeSafe && a != null; a = a.parentNode())
790 {
791 treeSafe = newChild != a;
792 }
793 if (!treeSafe) {
794 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
795 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
796 }
797 }
798
799 makeChildNode(); // make sure we have a node and not a string
800
801 // notify document
802 ownerDocument.insertingNode(this, replace);
803
804 // Convert to internal type, to avoid repeated casting
805 ChildNode newInternal = (ChildNode)newChild;
806
807 Node oldparent = newInternal.parentNode();
808 if (oldparent != null) {
809 oldparent.removeChild(newInternal);
810 }
811
812 // Convert to internal type, to avoid repeated casting
813 ChildNode refInternal = (ChildNode) refChild;
814
815 // Attach up
816 newInternal.ownerNode = this;
817 newInternal.isOwned(true);
818
819 // Attach before and after
820 // Note: firstChild.previousSibling == lastChild!!
821 ChildNode firstChild = (ChildNode) value;
822 if (firstChild == null) {
823 // this our first and only child
824 value = newInternal; // firstchild = newInternal;
825 newInternal.isFirstChild(true);
826 newInternal.previousSibling = newInternal;
827 }
828 else {
829 if (refInternal == null) {
830 // this is an append
831 ChildNode lastChild = firstChild.previousSibling;
832 lastChild.nextSibling = newInternal;
833 newInternal.previousSibling = lastChild;
834 firstChild.previousSibling = newInternal;
835 }
836 else {
837 // this is an insert
838 if (refChild == firstChild) {
839 // at the head of the list
840 firstChild.isFirstChild(false);
841 newInternal.nextSibling = firstChild;
842 newInternal.previousSibling = firstChild.previousSibling;
843 firstChild.previousSibling = newInternal;
844 value = newInternal; // firstChild = newInternal;
845 newInternal.isFirstChild(true);
846 }
847 else {
848 // somewhere in the middle
849 ChildNode prev = refInternal.previousSibling;
850 newInternal.nextSibling = refInternal;
851 prev.nextSibling = newInternal;
852 refInternal.previousSibling = newInternal;
853 newInternal.previousSibling = prev;
854 }
855 }
856 }
857
858 changed();
859
860 // notify document
861 ownerDocument.insertedNode(this, newInternal, replace);
862
863 checkNormalizationAfterInsert(newInternal);
864
865 return newChild;
866
867 } // internalInsertBefore(Node,Node,int):Node
868
869 /**
870 * Remove a child from this Node. The removed child's subtree
871 * remains intact so it may be re-inserted elsewhere.
872 *
873 * @return oldChild, in its new state (removed).
874 *
875 * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
876 * this node.
877 *
878 * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
879 * read-only.
880 */
881 public Node removeChild(Node oldChild)
882 throws DOMException {
883 // Tail-call, should be optimizable
884 if (hasStringValue()) {
885 // we don't have any child per say so it can't be one of them!
886 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
887 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
888 }
889 return internalRemoveChild(oldChild, false);
890 } // removeChild(Node) :Node
891
892 /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
893 * to control which mutation events are spawned. This version of the
894 * removeChild operation allows us to do so. It is not intended
895 * for use by application programs.
896 */
897 Node internalRemoveChild(Node oldChild, boolean replace)
898 throws DOMException {
899
900 CoreDocumentImpl ownerDocument = ownerDocument();
901 if (ownerDocument.errorChecking) {
902 if (isReadOnly()) {
903 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
904 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
905 }
906 if (oldChild != null && oldChild.getParentNode() != this) {
907 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
908 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
909 }
910 }
911
912 ChildNode oldInternal = (ChildNode) oldChild;
913
914 // notify document
915 ownerDocument.removingNode(this, oldInternal, replace);
916
917 // Patch linked list around oldChild
918 // Note: lastChild == firstChild.previousSibling
919 if (oldInternal == value) { // oldInternal == firstChild
920 // removing first child
921 oldInternal.isFirstChild(false);
922 // next line is: firstChild = oldInternal.nextSibling
923 value = oldInternal.nextSibling;
924 ChildNode firstChild = (ChildNode) value;
925 if (firstChild != null) {
926 firstChild.isFirstChild(true);
927 firstChild.previousSibling = oldInternal.previousSibling;
928 }
929 } else {
930 ChildNode prev = oldInternal.previousSibling;
931 ChildNode next = oldInternal.nextSibling;
932 prev.nextSibling = next;
933 if (next == null) {
934 // removing last child
935 ChildNode firstChild = (ChildNode) value;
936 firstChild.previousSibling = prev;
937 } else {
938 // removing some other child in the middle
939 next.previousSibling = prev;
940 }
941 }
942
943 // Save previous sibling for normalization checking.
944 ChildNode oldPreviousSibling = oldInternal.previousSibling();
945
946 // Remove oldInternal's references to tree
947 oldInternal.ownerNode = ownerDocument;
948 oldInternal.isOwned(false);
949 oldInternal.nextSibling = null;
950 oldInternal.previousSibling = null;
951
952 changed();
953
954 // notify document
955 ownerDocument.removedNode(this, replace);
956
957 checkNormalizationAfterRemove(oldPreviousSibling);
958
959 return oldInternal;
960
961 } // internalRemoveChild(Node,int):Node
962
963 /**
964 * Make newChild occupy the location that oldChild used to
965 * have. Note that newChild will first be removed from its previous
966 * parent, if any. Equivalent to inserting newChild before oldChild,
967 * then removing oldChild.
968 *
969 * @return oldChild, in its new state (removed).
970 *
971 * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
972 * type that shouldn't be a child of this node, or if newChild is
973 * one of our ancestors.
974 *
975 * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
976 * different owner document than we do.
977 *
978 * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
979 * this node.
980 *
981 * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
982 * read-only.
983 */
984 public Node replaceChild(Node newChild, Node oldChild)
985 throws DOMException {
986
987 makeChildNode();
988
989 // If Mutation Events are being generated, this operation might
990 // throw aggregate events twice when modifying an Attr -- once
991 // on insertion and once on removal. DOM Level 2 does not specify
992 // this as either desirable or undesirable, but hints that
993 // aggregations should be issued only once per user request.
994
995 // notify document
996 CoreDocumentImpl ownerDocument = ownerDocument();
997 ownerDocument.replacingNode(this);
998
999 internalInsertBefore(newChild, oldChild, true);
1000 if (newChild != oldChild) {
1001 internalRemoveChild(oldChild, true);
1002 }
1003
1004 // notify document
1005 ownerDocument.replacedNode(this);
1006
1007 return oldChild;
1008 }
1009
1010 //
1011 // NodeList methods
1012 //
1013
1014 /**
1015 * NodeList method: Count the immediate children of this node
1016 * @return int
1017 */
1018 public int getLength() {
1019
1020 if (hasStringValue()) {
1021 return 1;
1022 }
1023 ChildNode node = (ChildNode) value;
1024 int length = 0;
1025 for (; node != null; node = node.nextSibling) {
1026 length++;
1027 }
1028 return length;
1029
1030 } // getLength():int
1031
1032 /**
1033 * NodeList method: Return the Nth immediate child of this node, or
1034 * null if the index is out of bounds.
1035 * @return org.w3c.dom.Node
1036 * @param Index int
1037 */
1038 public Node item(int index) {
1039
1040 if (hasStringValue()) {
1041 if (index != 0 || value == null) {
1042 return null;
1043 }
1044 else {
1045 makeChildNode();
1046 return (Node) value;
1047 }
1048 }
1049 if (index < 0) {
1050 return null;
1051 }
1052 ChildNode node = (ChildNode) value;
1053 for (int i = 0; i < index && node != null; i++) {
1054 node = node.nextSibling;
1055 }
1056 return node;
1057
1058 } // item(int):Node
1059
1060 //
1061 // DOM3
1062 //
1063
1064 /**
1065 * DOM Level 3 WD- Experimental.
1066 * Override inherited behavior from ParentNode to support deep equal.
1067 * isEqualNode is always deep on Attr nodes.
1068 */
1069 public boolean isEqualNode(Node arg) {
1070 return super.isEqualNode(arg);
1071 }
1072
1073 /**
1074 * Introduced in DOM Level 3. <p>
1075 * Checks if a type is derived from another by restriction. See:
1076 * http://www.w3.org/TR/DOM-Level-3-Core/core.html#TypeInfo-isDerivedFrom
1077 *
1078 * @param ancestorNS
1079 * The namspace of the ancestor type declaration
1080 * @param ancestorName
1081 * The name of the ancestor type declaration
1082 * @param type
1083 * The reference type definition
1084 *
1085 * @return boolean True if the type is derived by restriciton for the
1086 * reference type
1087 */
1088 public boolean isDerivedFrom(String typeNamespaceArg,
1089 String typeNameArg,
1090 int derivationMethod) {
1091
1092 return false;
1093 }
1094
1095
1096 //
1097 // Public methods
1098 //
1099
1100 /**
1101 * Override default behavior so that if deep is true, children are also
1102 * toggled.
1103 * @see Node
1104 * <P>
1105 * Note: this will not change the state of an EntityReference or its
1106 * children, which are always read-only.
1107 */
1108 public void setReadOnly(boolean readOnly, boolean deep) {
1109
1110 super.setReadOnly(readOnly, deep);
1111
1112 if (deep) {
1113
1114 if (needsSyncChildren()) {
1115 synchronizeChildren();
1116 }
1117
1118 if (hasStringValue()) {
1119 return;
1120 }
1121 // Recursively set kids
1122 for (ChildNode mykid = (ChildNode) value;
1123 mykid != null;
1124 mykid = mykid.nextSibling) {
1125 if (mykid.getNodeType() != Node.ENTITY_REFERENCE_NODE) {
1126 mykid.setReadOnly(readOnly,true);
1127 }
1128 }
1129 }
1130 } // setReadOnly(boolean,boolean)
1131
1132 //
1133 // Protected methods
1134 //
1135
1136 /**
1137 * Override this method in subclass to hook in efficient
1138 * internal data structure.
1139 */
1140 protected void synchronizeChildren() {
1141 // By default just change the flag to avoid calling this method again
1142 needsSyncChildren(false);
1143 }
1144
1145 /**
1146 * Checks the normalized state of this node after inserting a child.
1147 * If the inserted child causes this node to be unnormalized, then this
1148 * node is flagged accordingly.
1149 * The conditions for changing the normalized state are:
1150 * <ul>
1151 * <li>The inserted child is a text node and one of its adjacent siblings
1152 * is also a text node.
1153 * <li>The inserted child is is itself unnormalized.
1154 * </ul>
1155 *
1156 * @param insertedChild the child node that was inserted into this node
1157 *
1158 * @throws NullPointerException if the inserted child is <code>null</code>
1159 */
1160 void checkNormalizationAfterInsert(ChildNode insertedChild) {
1161 // See if insertion caused this node to be unnormalized.
1162 if (insertedChild.getNodeType() == Node.TEXT_NODE) {
1163 ChildNode prev = insertedChild.previousSibling();
1164 ChildNode next = insertedChild.nextSibling;
1165 // If an adjacent sibling of the new child is a text node,
1166 // flag this node as unnormalized.
1167 if ((prev != null && prev.getNodeType() == Node.TEXT_NODE) ||
1168 (next != null && next.getNodeType() == Node.TEXT_NODE)) {
1169 isNormalized(false);
1170 }
1171 }
1172 else {
1173 // If the new child is not normalized,
1174 // then this node is inherently not normalized.
1175 if (!insertedChild.isNormalized()) {
1176 isNormalized(false);
1177 }
1178 }
1179 } // checkNormalizationAfterInsert(ChildNode)
1180
1181 /**
1182 * Checks the normalized of this node after removing a child.
1183 * If the removed child causes this node to be unnormalized, then this
1184 * node is flagged accordingly.
1185 * The conditions for changing the normalized state are:
1186 * <ul>
1187 * <li>The removed child had two adjacent siblings that were text nodes.
1188 * </ul>
1189 *
1190 * @param previousSibling the previous sibling of the removed child, or
1191 * <code>null</code>
1192 */
1193 void checkNormalizationAfterRemove(ChildNode previousSibling) {
1194 // See if removal caused this node to be unnormalized.
1195 // If the adjacent siblings of the removed child were both text nodes,
1196 // flag this node as unnormalized.
1197 if (previousSibling != null &&
1198 previousSibling.getNodeType() == Node.TEXT_NODE) {
1199
1200 ChildNode next = previousSibling.nextSibling;
1201 if (next != null && next.getNodeType() == Node.TEXT_NODE) {
1202 isNormalized(false);
1203 }
1204 }
1205 } // checkNormalizationAfterRemove(ChildNode)
1206
1207 //
1208 // Serialization methods
1209 //
1210
1211 /** Serialize object. */
1212 private void writeObject(ObjectOutputStream out) throws IOException {
1213
1214 // synchronize chilren
1215 if (needsSyncChildren()) {
1216 synchronizeChildren();
1217 }
1218 // write object
1219 out.defaultWriteObject();
1220
1221 } // writeObject(ObjectOutputStream)
1222
1223 /** Deserialize object. */
1224 private void readObject(ObjectInputStream ois)
1225 throws ClassNotFoundException, IOException {
1226
1227 // perform default deseralization
1228 ois.defaultReadObject();
1229
1230 // hardset synchildren - so we don't try to sync -
1231 // it does not make any sense to try to synchildren when we just
1232 // deserialize object.
1233 needsSyncChildren(false);
1234
1235 } // readObject(ObjectInputStream)
1236
1237
1238 } // class AttrImpl