0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.xerces.dom;
0019:
0020: import java.io.IOException;
0021: import java.io.ObjectInputStream;
0022: import java.io.ObjectOutputStream;
0023:
0024: import org.w3c.dom.TypeInfo;
0025: import org.w3c.dom.Attr;
0026: import org.w3c.dom.DOMException;
0027: import org.w3c.dom.Element;
0028: import org.w3c.dom.Node;
0029: import org.w3c.dom.NodeList;
0030: import org.w3c.dom.Text;
0031:
0032: /**
0033: * Attribute represents an XML-style attribute of an
0034: * Element. Typically, the allowable values are controlled by its
0035: * declaration in the Document Type Definition (DTD) governing this
0036: * kind of document.
0037: * <P>
0038: * If the attribute has not been explicitly assigned a value, but has
0039: * been declared in the DTD, it will exist and have that default. Only
0040: * if neither the document nor the DTD specifies a value will the
0041: * Attribute really be considered absent and have no value; in that
0042: * case, querying the attribute will return null.
0043: * <P>
0044: * Attributes may have multiple children that contain their data. (XML
0045: * allows attributes to contain entity references, and tokenized
0046: * attribute types such as NMTOKENS may have a child for each token.)
0047: * For convenience, the Attribute object's getValue() method returns
0048: * the string version of the attribute's value.
0049: * <P>
0050: * Attributes are not children of the Elements they belong to, in the
0051: * usual sense, and have no valid Parent reference. However, the spec
0052: * says they _do_ belong to a specific Element, and an INUSE exception
0053: * is to be thrown if the user attempts to explicitly share them
0054: * between elements.
0055: * <P>
0056: * Note that Elements do not permit attributes to appear to be shared
0057: * (see the INUSE exception), so this object's mutability is
0058: * officially not an issue.
0059: * <p>
0060: * Note: The ownerNode attribute is used to store the Element the Attr
0061: * node is associated with. Attr nodes do not have parent nodes.
0062: * Besides, the getOwnerElement() method can be used to get the element node
0063: * this attribute is associated with.
0064: * <P>
0065: * AttrImpl does not support Namespaces. AttrNSImpl, which inherits from
0066: * it, does.
0067: *
0068: * <p>AttrImpl used to inherit from ParentNode. It now directly inherits from
0069: * NodeImpl and provide its own implementation of the ParentNode's behavior.
0070: * The reason is that we now try and avoid to always create a Text node to
0071: * hold the value of an attribute. The DOM spec requires it, so we still have
0072: * to do it in case getFirstChild() is called for instance. The reason
0073: * attribute values are stored as a list of nodes is so that they can carry
0074: * more than a simple string. They can also contain EntityReference nodes.
0075: * However, most of the times people only have a single string that they only
0076: * set and get through Element.set/getAttribute or Attr.set/getValue. In this
0077: * new version, the Attr node has a value pointer which can either be the
0078: * String directly or a pointer to the first ChildNode. A flag tells which one
0079: * it currently is. Note that while we try to stick with the direct String as
0080: * much as possible once we've switched to a node there is no going back. This
0081: * is because we have no way to know whether the application keeps referring to
0082: * the node we once returned.
0083: * <p> The gain in memory varies on the density of attributes in the document.
0084: * But in the tests I've run I've seen up to 12% of memory gain. And the good
0085: * thing is that it also leads to a slight gain in speed because we allocate
0086: * fewer objects! I mean, that's until we have to actually create the node...
0087: * <p>
0088: * To avoid too much duplicated code, I got rid of ParentNode and renamed
0089: * ChildAndParentNode, which I never really liked, to ParentNode for
0090: * simplicity, this doesn't make much of a difference in memory usage because
0091: * there are only very few objects that are only a Parent. This is only true
0092: * now because AttrImpl now inherits directly from NodeImpl and has its own
0093: * implementation of the ParentNode's node behavior. So there is still some
0094: * duplicated code there.
0095: * <p>
0096: * This class doesn't directly support mutation events, however, it notifies
0097: * the document when mutations are performed so that the document class do so.
0098: *
0099: * <p><b>WARNING</b>: Some of the code here is partially duplicated in
0100: * ParentNode, be careful to keep these two classes in sync!
0101: *
0102: * @xerces.internal
0103: *
0104: * @see AttrNSImpl
0105: *
0106: * @author Arnaud Le Hors, IBM
0107: * @author Joe Kesselman, IBM
0108: * @author Andy Clark, IBM
0109: * @version $Id: AttrImpl.java 449328 2006-09-23 22:58:23Z mrglavas $
0110: * @since PR-DOM-Level-1-19980818.
0111: *
0112: */
0113: public class AttrImpl extends NodeImpl implements Attr, TypeInfo {
0114:
0115: //
0116: // Constants
0117: //
0118:
0119: /** Serialization version. */
0120: static final long serialVersionUID = 7277707688218972102L;
0121:
0122: /** DTD namespace. **/
0123: static final String DTD_URI = "http://www.w3.org/TR/REC-xml";
0124:
0125: //
0126: // Data
0127: //
0128:
0129: /** This can either be a String or the first child node. */
0130: protected Object value = null;
0131:
0132: /** Attribute name. */
0133: protected String name;
0134:
0135: /** Type information */
0136: // REVISIT: we are losing the type information in DOM during serialization
0137: transient Object type;
0138:
0139: protected static TextImpl textNode = null;
0140:
0141: //
0142: // Constructors
0143: //
0144:
0145: /**
0146: * Attribute has no public constructor. Please use the factory
0147: * method in the Document class.
0148: */
0149: protected AttrImpl(CoreDocumentImpl ownerDocument, String name) {
0150: super (ownerDocument);
0151: this .name = name;
0152: /** False for default attributes. */
0153: isSpecified(true);
0154: hasStringValue(true);
0155: }
0156:
0157: // for AttrNSImpl
0158: protected AttrImpl() {
0159: }
0160:
0161: // Support for DOM Level 3 renameNode method.
0162: // Note: This only deals with part of the pb. It is expected to be
0163: // called after the Attr has been detached for one thing.
0164: // CoreDocumentImpl does all the work.
0165: void rename(String name) {
0166: if (needsSyncData()) {
0167: synchronizeData();
0168: }
0169: this .name = name;
0170: }
0171:
0172: // create a real text node as child if we don't have one yet
0173: protected void makeChildNode() {
0174: if (hasStringValue()) {
0175: if (value != null) {
0176: TextImpl text = (TextImpl) ownerDocument()
0177: .createTextNode((String) value);
0178: value = text;
0179: text.isFirstChild(true);
0180: text.previousSibling = text;
0181: text.ownerNode = this ;
0182: text.isOwned(true);
0183: }
0184: hasStringValue(false);
0185: }
0186: }
0187:
0188: /**
0189: * NON-DOM
0190: * set the ownerDocument of this node and its children
0191: */
0192: protected void setOwnerDocument(CoreDocumentImpl doc) {
0193: if (needsSyncChildren()) {
0194: synchronizeChildren();
0195: }
0196: super .setOwnerDocument(doc);
0197: if (!hasStringValue()) {
0198: for (ChildNode child = (ChildNode) value; child != null; child = child.nextSibling) {
0199: child.setOwnerDocument(doc);
0200: }
0201: }
0202: }
0203:
0204: /**
0205: * NON-DOM: set the type of this attribute to be ID type.
0206: *
0207: * @param id
0208: */
0209: public void setIdAttribute(boolean id) {
0210: if (needsSyncData()) {
0211: synchronizeData();
0212: }
0213: isIdAttribute(id);
0214: }
0215:
0216: /** DOM Level 3: isId*/
0217: public boolean isId() {
0218: // REVISIT: should an attribute that is not in the tree return
0219: // isID true?
0220: return isIdAttribute();
0221: }
0222:
0223: //
0224: // Node methods
0225: //
0226:
0227: public Node cloneNode(boolean deep) {
0228:
0229: if (needsSyncChildren()) {
0230: synchronizeChildren();
0231: }
0232: AttrImpl clone = (AttrImpl) super .cloneNode(deep);
0233:
0234: // take care of case where there are kids
0235: if (!clone.hasStringValue()) {
0236:
0237: // Need to break the association w/ original kids
0238: clone.value = null;
0239:
0240: // Cloning an Attribute always clones its children,
0241: // since they represent its value, no matter whether this
0242: // is a deep clone or not
0243: for (Node child = (Node) value; child != null; child = child
0244: .getNextSibling()) {
0245: clone.appendChild(child.cloneNode(true));
0246: }
0247: }
0248: clone.isSpecified(true);
0249: return clone;
0250: }
0251:
0252: /**
0253: * A short integer indicating what type of node this is. The named
0254: * constants for this value are defined in the org.w3c.dom.Node interface.
0255: */
0256: public short getNodeType() {
0257: return Node.ATTRIBUTE_NODE;
0258: }
0259:
0260: /**
0261: * Returns the attribute name
0262: */
0263: public String getNodeName() {
0264: if (needsSyncData()) {
0265: synchronizeData();
0266: }
0267: return name;
0268: }
0269:
0270: /**
0271: * Implicit in the rerouting of getNodeValue to getValue is the
0272: * need to redefine setNodeValue, for symmetry's sake. Note that
0273: * since we're explicitly providing a value, Specified should be set
0274: * true.... even if that value equals the default.
0275: */
0276: public void setNodeValue(String value) throws DOMException {
0277: setValue(value);
0278: }
0279:
0280: /**
0281: * @see org.w3c.dom.TypeInfo#getTypeName()
0282: */
0283: public String getTypeName() {
0284: return (String) type;
0285: }
0286:
0287: /**
0288: * @see org.w3c.dom.TypeInfo#getTypeNamespace()
0289: */
0290: public String getTypeNamespace() {
0291: if (type != null) {
0292: return DTD_URI;
0293: }
0294: return null;
0295: }
0296:
0297: /**
0298: * Method getSchemaTypeInfo.
0299: * @return TypeInfo
0300: */
0301: public TypeInfo getSchemaTypeInfo() {
0302: return this ;
0303: }
0304:
0305: /**
0306: * In Attribute objects, NodeValue is considered a synonym for
0307: * Value.
0308: *
0309: * @see #getValue()
0310: */
0311: public String getNodeValue() {
0312: return getValue();
0313: }
0314:
0315: //
0316: // Attr methods
0317: //
0318:
0319: /**
0320: * In Attributes, NodeName is considered a synonym for the
0321: * attribute's Name
0322: */
0323: public String getName() {
0324:
0325: if (needsSyncData()) {
0326: synchronizeData();
0327: }
0328: return name;
0329:
0330: } // getName():String
0331:
0332: /**
0333: * The DOM doesn't clearly define what setValue(null) means. I've taken it
0334: * as "remove all children", which from outside should appear
0335: * similar to setting it to the empty string.
0336: */
0337: public void setValue(String newvalue) {
0338:
0339: CoreDocumentImpl ownerDocument = ownerDocument();
0340:
0341: if (ownerDocument.errorChecking && isReadOnly()) {
0342: String msg = DOMMessageFormatter.formatMessage(
0343: DOMMessageFormatter.DOM_DOMAIN,
0344: "NO_MODIFICATION_ALLOWED_ERR", null);
0345: throw new DOMException(
0346: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
0347: }
0348:
0349: Element ownerElement = getOwnerElement();
0350: String oldvalue = "";
0351: if (needsSyncData()) {
0352: synchronizeData();
0353: }
0354: if (needsSyncChildren()) {
0355: synchronizeChildren();
0356: }
0357: if (value != null) {
0358: if (ownerDocument.getMutationEvents()) {
0359: // Can no longer just discard the kids; they may have
0360: // event listeners waiting for them to disconnect.
0361: if (hasStringValue()) {
0362: oldvalue = (String) value;
0363: // create an actual text node as our child so
0364: // that we can use it in the event
0365: if (textNode == null) {
0366: textNode = (TextImpl) ownerDocument
0367: .createTextNode((String) value);
0368: } else {
0369: textNode.data = (String) value;
0370: }
0371: value = textNode;
0372: textNode.isFirstChild(true);
0373: textNode.previousSibling = textNode;
0374: textNode.ownerNode = this ;
0375: textNode.isOwned(true);
0376: hasStringValue(false);
0377: internalRemoveChild(textNode, true);
0378: } else {
0379: oldvalue = getValue();
0380: while (value != null) {
0381: internalRemoveChild((Node) value, true);
0382: }
0383: }
0384: } else {
0385: if (hasStringValue()) {
0386: oldvalue = (String) value;
0387: } else {
0388: // simply discard children if any
0389: oldvalue = getValue();
0390: // remove ref from first child to last child
0391: ChildNode firstChild = (ChildNode) value;
0392: firstChild.previousSibling = null;
0393: firstChild.isFirstChild(false);
0394: firstChild.ownerNode = ownerDocument;
0395: }
0396: // then remove ref to current value
0397: value = null;
0398: needsSyncChildren(false);
0399: }
0400: if (isIdAttribute() && ownerElement != null) {
0401: ownerDocument.removeIdentifier(oldvalue);
0402: }
0403: }
0404:
0405: // Create and add the new one, generating only non-aggregate events
0406: // (There are no listeners on the new Text, but there may be
0407: // capture/bubble listeners on the Attr.
0408: // Note that aggregate events are NOT dispatched here,
0409: // since we need to combine the remove and insert.
0410: isSpecified(true);
0411: if (ownerDocument.getMutationEvents()) {
0412: // if there are any event handlers create a real node
0413: internalInsertBefore(
0414: ownerDocument.createTextNode(newvalue), null, true);
0415: hasStringValue(false);
0416: // notify document
0417: ownerDocument.modifiedAttrValue(this , oldvalue);
0418: } else {
0419: // directly store the string
0420: value = newvalue;
0421: hasStringValue(true);
0422: changed();
0423: }
0424: if (isIdAttribute() && ownerElement != null) {
0425: ownerDocument.putIdentifier(newvalue, ownerElement);
0426: }
0427:
0428: } // setValue(String)
0429:
0430: /**
0431: * The "string value" of an Attribute is its text representation,
0432: * which in turn is a concatenation of the string values of its children.
0433: */
0434: public String getValue() {
0435:
0436: if (needsSyncData()) {
0437: synchronizeData();
0438: }
0439: if (needsSyncChildren()) {
0440: synchronizeChildren();
0441: }
0442: if (value == null) {
0443: return "";
0444: }
0445: if (hasStringValue()) {
0446: return (String) value;
0447: }
0448:
0449: ChildNode firstChild = ((ChildNode) value);
0450:
0451: String data = null;
0452: if (firstChild.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
0453: data = ((EntityReferenceImpl) firstChild)
0454: .getEntityRefValue();
0455: } else {
0456: data = firstChild.getNodeValue();
0457: }
0458:
0459: ChildNode node = firstChild.nextSibling;
0460:
0461: if (node == null || data == null)
0462: return (data == null) ? "" : data;
0463:
0464: StringBuffer value = new StringBuffer(data);
0465: while (node != null) {
0466: if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
0467: data = ((EntityReferenceImpl) node).getEntityRefValue();
0468: if (data == null)
0469: return "";
0470: value.append(data);
0471: } else {
0472: value.append(node.getNodeValue());
0473: }
0474: node = node.nextSibling;
0475: }
0476: return value.toString();
0477:
0478: } // getValue():String
0479:
0480: /**
0481: * The "specified" flag is true if and only if this attribute's
0482: * value was explicitly specified in the original document. Note that
0483: * the implementation, not the user, is in charge of this
0484: * property. If the user asserts an Attribute value (even if it ends
0485: * up having the same value as the default), it is considered a
0486: * specified attribute. If you really want to revert to the default,
0487: * delete the attribute from the Element, and the Implementation will
0488: * re-assert the default (if any) in its place, with the appropriate
0489: * specified=false setting.
0490: */
0491: public boolean getSpecified() {
0492:
0493: if (needsSyncData()) {
0494: synchronizeData();
0495: }
0496: return isSpecified();
0497:
0498: } // getSpecified():boolean
0499:
0500: //
0501: // Attr2 methods
0502: //
0503:
0504: /**
0505: * Returns the element node that this attribute is associated with,
0506: * or null if the attribute has not been added to an element.
0507: *
0508: * @see #getOwnerElement
0509: *
0510: * @deprecated Previous working draft of DOM Level 2. New method
0511: * is <tt>getOwnerElement()</tt>.
0512: */
0513: public Element getElement() {
0514: // if we have an owner, ownerNode is our ownerElement, otherwise it's
0515: // our ownerDocument and we don't have an ownerElement
0516: return (Element) (isOwned() ? ownerNode : null);
0517: }
0518:
0519: /**
0520: * Returns the element node that this attribute is associated with,
0521: * or null if the attribute has not been added to an element.
0522: *
0523: * @since WD-DOM-Level-2-19990719
0524: */
0525: public Element getOwnerElement() {
0526: // if we have an owner, ownerNode is our ownerElement, otherwise it's
0527: // our ownerDocument and we don't have an ownerElement
0528: return (Element) (isOwned() ? ownerNode : null);
0529: }
0530:
0531: public void normalize() {
0532:
0533: // No need to normalize if already normalized or
0534: // if value is kept as a String.
0535: if (isNormalized() || hasStringValue())
0536: return;
0537:
0538: Node kid, next;
0539: ChildNode firstChild = (ChildNode) value;
0540: for (kid = firstChild; kid != null; kid = next) {
0541: next = kid.getNextSibling();
0542:
0543: // If kid is a text node, we need to check for one of two
0544: // conditions:
0545: // 1) There is an adjacent text node
0546: // 2) There is no adjacent text node, but kid is
0547: // an empty text node.
0548: if (kid.getNodeType() == Node.TEXT_NODE) {
0549: // If an adjacent text node, merge it with kid
0550: if (next != null
0551: && next.getNodeType() == Node.TEXT_NODE) {
0552: ((Text) kid).appendData(next.getNodeValue());
0553: removeChild(next);
0554: next = kid; // Don't advance; there might be another.
0555: } else {
0556: // If kid is empty, remove it
0557: if (kid.getNodeValue() == null
0558: || kid.getNodeValue().length() == 0) {
0559: removeChild(kid);
0560: }
0561: }
0562: }
0563: }
0564:
0565: isNormalized(true);
0566: } // normalize()
0567:
0568: //
0569: // Public methods
0570: //
0571:
0572: /** NON-DOM, for use by parser */
0573: public void setSpecified(boolean arg) {
0574:
0575: if (needsSyncData()) {
0576: synchronizeData();
0577: }
0578: isSpecified(arg);
0579:
0580: } // setSpecified(boolean)
0581:
0582: /**
0583: * NON-DOM: used by the parser
0584: * @param type
0585: */
0586: public void setType(Object type) {
0587: this .type = type;
0588: }
0589:
0590: //
0591: // Object methods
0592: //
0593:
0594: /** NON-DOM method for debugging convenience */
0595: public String toString() {
0596: return getName() + "=" + "\"" + getValue() + "\"";
0597: }
0598:
0599: /**
0600: * Test whether this node has any children. Convenience shorthand
0601: * for (Node.getFirstChild()!=null)
0602: */
0603: public boolean hasChildNodes() {
0604: if (needsSyncChildren()) {
0605: synchronizeChildren();
0606: }
0607: return value != null;
0608: }
0609:
0610: /**
0611: * Obtain a NodeList enumerating all children of this node. If there
0612: * are none, an (initially) empty NodeList is returned.
0613: * <p>
0614: * NodeLists are "live"; as children are added/removed the NodeList
0615: * will immediately reflect those changes. Also, the NodeList refers
0616: * to the actual nodes, so changes to those nodes made via the DOM tree
0617: * will be reflected in the NodeList and vice versa.
0618: * <p>
0619: * In this implementation, Nodes implement the NodeList interface and
0620: * provide their own getChildNodes() support. Other DOMs may solve this
0621: * differently.
0622: */
0623: public NodeList getChildNodes() {
0624: // JKESS: KNOWN ISSUE HERE
0625:
0626: if (needsSyncChildren()) {
0627: synchronizeChildren();
0628: }
0629: return this ;
0630:
0631: } // getChildNodes():NodeList
0632:
0633: /** The first child of this Node, or null if none. */
0634: public Node getFirstChild() {
0635:
0636: if (needsSyncChildren()) {
0637: synchronizeChildren();
0638: }
0639: makeChildNode();
0640: return (Node) value;
0641:
0642: } // getFirstChild():Node
0643:
0644: /** The last child of this Node, or null if none. */
0645: public Node getLastChild() {
0646:
0647: if (needsSyncChildren()) {
0648: synchronizeChildren();
0649: }
0650: return lastChild();
0651:
0652: } // getLastChild():Node
0653:
0654: final ChildNode lastChild() {
0655: // last child is stored as the previous sibling of first child
0656: makeChildNode();
0657: return value != null ? ((ChildNode) value).previousSibling
0658: : null;
0659: }
0660:
0661: final void lastChild(ChildNode node) {
0662: // store lastChild as previous sibling of first child
0663: if (value != null) {
0664: ((ChildNode) value).previousSibling = node;
0665: }
0666: }
0667:
0668: /**
0669: * Move one or more node(s) to our list of children. Note that this
0670: * implicitly removes them from their previous parent.
0671: *
0672: * @param newChild The Node to be moved to our subtree. As a
0673: * convenience feature, inserting a DocumentNode will instead insert
0674: * all its children.
0675: *
0676: * @param refChild Current child which newChild should be placed
0677: * immediately before. If refChild is null, the insertion occurs
0678: * after all existing Nodes, like appendChild().
0679: *
0680: * @return newChild, in its new state (relocated, or emptied in the case of
0681: * DocumentNode.)
0682: *
0683: * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
0684: * type that shouldn't be a child of this node, or if newChild is an
0685: * ancestor of this node.
0686: *
0687: * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
0688: * different owner document than we do.
0689: *
0690: * @throws DOMException(NOT_FOUND_ERR) if refChild is not a child of
0691: * this node.
0692: *
0693: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
0694: * read-only.
0695: */
0696: public Node insertBefore(Node newChild, Node refChild)
0697: throws DOMException {
0698: // Tail-call; optimizer should be able to do good things with.
0699: return internalInsertBefore(newChild, refChild, false);
0700: } // insertBefore(Node,Node):Node
0701:
0702: /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
0703: * to control which mutation events are spawned. This version of the
0704: * insertBefore operation allows us to do so. It is not intended
0705: * for use by application programs.
0706: */
0707: Node internalInsertBefore(Node newChild, Node refChild,
0708: boolean replace) throws DOMException {
0709:
0710: CoreDocumentImpl ownerDocument = ownerDocument();
0711: boolean errorChecking = ownerDocument.errorChecking;
0712:
0713: if (newChild.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE) {
0714: // SLOW BUT SAFE: We could insert the whole subtree without
0715: // juggling so many next/previous pointers. (Wipe out the
0716: // parent's child-list, patch the parent pointers, set the
0717: // ends of the list.) But we know some subclasses have special-
0718: // case behavior they add to insertBefore(), so we don't risk it.
0719: // This approch also takes fewer bytecodes.
0720:
0721: // NOTE: If one of the children is not a legal child of this
0722: // node, throw HIERARCHY_REQUEST_ERR before _any_ of the children
0723: // have been transferred. (Alternative behaviors would be to
0724: // reparent up to the first failure point or reparent all those
0725: // which are acceptable to the target node, neither of which is
0726: // as robust. PR-DOM-0818 isn't entirely clear on which it
0727: // recommends?????
0728:
0729: // No need to check kids for right-document; if they weren't,
0730: // they wouldn't be kids of that DocFrag.
0731: if (errorChecking) {
0732: for (Node kid = newChild.getFirstChild(); // Prescan
0733: kid != null; kid = kid.getNextSibling()) {
0734:
0735: if (!ownerDocument.isKidOK(this , kid)) {
0736: String msg = DOMMessageFormatter.formatMessage(
0737: DOMMessageFormatter.DOM_DOMAIN,
0738: "HIERARCHY_REQUEST_ERR", null);
0739: throw new DOMException(
0740: DOMException.HIERARCHY_REQUEST_ERR, msg);
0741: }
0742: }
0743: }
0744:
0745: while (newChild.hasChildNodes()) {
0746: insertBefore(newChild.getFirstChild(), refChild);
0747: }
0748: return newChild;
0749: }
0750:
0751: if (newChild == refChild) {
0752: // stupid case that must be handled as a no-op triggering events...
0753: refChild = refChild.getNextSibling();
0754: removeChild(newChild);
0755: insertBefore(newChild, refChild);
0756: return newChild;
0757: }
0758:
0759: if (needsSyncChildren()) {
0760: synchronizeChildren();
0761: }
0762:
0763: if (errorChecking) {
0764: if (isReadOnly()) {
0765: String msg = DOMMessageFormatter.formatMessage(
0766: DOMMessageFormatter.DOM_DOMAIN,
0767: "NO_MODIFICATION_ALLOWED_ERR", null);
0768: throw new DOMException(
0769: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
0770: }
0771: if (newChild.getOwnerDocument() != ownerDocument) {
0772: String msg = DOMMessageFormatter.formatMessage(
0773: DOMMessageFormatter.DOM_DOMAIN,
0774: "WRONG_DOCUMENT_ERR", null);
0775: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
0776: msg);
0777: }
0778: if (!ownerDocument.isKidOK(this , newChild)) {
0779: String msg = DOMMessageFormatter.formatMessage(
0780: DOMMessageFormatter.DOM_DOMAIN,
0781: "HIERARCHY_REQUEST_ERR", null);
0782: throw new DOMException(
0783: DOMException.HIERARCHY_REQUEST_ERR, msg);
0784: }
0785: // refChild must be a child of this node (or null)
0786: if (refChild != null && refChild.getParentNode() != this ) {
0787: String msg = DOMMessageFormatter.formatMessage(
0788: DOMMessageFormatter.DOM_DOMAIN,
0789: "NOT_FOUND_ERR", null);
0790: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
0791: }
0792:
0793: // Prevent cycles in the tree
0794: // newChild cannot be ancestor of this Node,
0795: // and actually cannot be this
0796: boolean treeSafe = true;
0797: for (NodeImpl a = this ; treeSafe && a != null; a = a
0798: .parentNode()) {
0799: treeSafe = newChild != a;
0800: }
0801: if (!treeSafe) {
0802: String msg = DOMMessageFormatter.formatMessage(
0803: DOMMessageFormatter.DOM_DOMAIN,
0804: "HIERARCHY_REQUEST_ERR", null);
0805: throw new DOMException(
0806: DOMException.HIERARCHY_REQUEST_ERR, msg);
0807: }
0808: }
0809:
0810: makeChildNode(); // make sure we have a node and not a string
0811:
0812: // notify document
0813: ownerDocument.insertingNode(this , replace);
0814:
0815: // Convert to internal type, to avoid repeated casting
0816: ChildNode newInternal = (ChildNode) newChild;
0817:
0818: Node oldparent = newInternal.parentNode();
0819: if (oldparent != null) {
0820: oldparent.removeChild(newInternal);
0821: }
0822:
0823: // Convert to internal type, to avoid repeated casting
0824: ChildNode refInternal = (ChildNode) refChild;
0825:
0826: // Attach up
0827: newInternal.ownerNode = this ;
0828: newInternal.isOwned(true);
0829:
0830: // Attach before and after
0831: // Note: firstChild.previousSibling == lastChild!!
0832: ChildNode firstChild = (ChildNode) value;
0833: if (firstChild == null) {
0834: // this our first and only child
0835: value = newInternal; // firstchild = newInternal;
0836: newInternal.isFirstChild(true);
0837: newInternal.previousSibling = newInternal;
0838: } else {
0839: if (refInternal == null) {
0840: // this is an append
0841: ChildNode lastChild = firstChild.previousSibling;
0842: lastChild.nextSibling = newInternal;
0843: newInternal.previousSibling = lastChild;
0844: firstChild.previousSibling = newInternal;
0845: } else {
0846: // this is an insert
0847: if (refChild == firstChild) {
0848: // at the head of the list
0849: firstChild.isFirstChild(false);
0850: newInternal.nextSibling = firstChild;
0851: newInternal.previousSibling = firstChild.previousSibling;
0852: firstChild.previousSibling = newInternal;
0853: value = newInternal; // firstChild = newInternal;
0854: newInternal.isFirstChild(true);
0855: } else {
0856: // somewhere in the middle
0857: ChildNode prev = refInternal.previousSibling;
0858: newInternal.nextSibling = refInternal;
0859: prev.nextSibling = newInternal;
0860: refInternal.previousSibling = newInternal;
0861: newInternal.previousSibling = prev;
0862: }
0863: }
0864: }
0865:
0866: changed();
0867:
0868: // notify document
0869: ownerDocument.insertedNode(this , newInternal, replace);
0870:
0871: checkNormalizationAfterInsert(newInternal);
0872:
0873: return newChild;
0874:
0875: } // internalInsertBefore(Node,Node,int):Node
0876:
0877: /**
0878: * Remove a child from this Node. The removed child's subtree
0879: * remains intact so it may be re-inserted elsewhere.
0880: *
0881: * @return oldChild, in its new state (removed).
0882: *
0883: * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
0884: * this node.
0885: *
0886: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
0887: * read-only.
0888: */
0889: public Node removeChild(Node oldChild) throws DOMException {
0890: // Tail-call, should be optimizable
0891: if (hasStringValue()) {
0892: // we don't have any child per say so it can't be one of them!
0893: String msg = DOMMessageFormatter.formatMessage(
0894: DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR",
0895: null);
0896: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
0897: }
0898: return internalRemoveChild(oldChild, false);
0899: } // removeChild(Node) :Node
0900:
0901: /** NON-DOM INTERNAL: Within DOM actions,we sometimes need to be able
0902: * to control which mutation events are spawned. This version of the
0903: * removeChild operation allows us to do so. It is not intended
0904: * for use by application programs.
0905: */
0906: Node internalRemoveChild(Node oldChild, boolean replace)
0907: throws DOMException {
0908:
0909: CoreDocumentImpl ownerDocument = ownerDocument();
0910: if (ownerDocument.errorChecking) {
0911: if (isReadOnly()) {
0912: String msg = DOMMessageFormatter.formatMessage(
0913: DOMMessageFormatter.DOM_DOMAIN,
0914: "NO_MODIFICATION_ALLOWED_ERR", null);
0915: throw new DOMException(
0916: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
0917: }
0918: if (oldChild != null && oldChild.getParentNode() != this ) {
0919: String msg = DOMMessageFormatter.formatMessage(
0920: DOMMessageFormatter.DOM_DOMAIN,
0921: "NOT_FOUND_ERR", null);
0922: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
0923: }
0924: }
0925:
0926: ChildNode oldInternal = (ChildNode) oldChild;
0927:
0928: // notify document
0929: ownerDocument.removingNode(this , oldInternal, replace);
0930:
0931: // Patch linked list around oldChild
0932: // Note: lastChild == firstChild.previousSibling
0933: if (oldInternal == value) { // oldInternal == firstChild
0934: // removing first child
0935: oldInternal.isFirstChild(false);
0936: // next line is: firstChild = oldInternal.nextSibling
0937: value = oldInternal.nextSibling;
0938: ChildNode firstChild = (ChildNode) value;
0939: if (firstChild != null) {
0940: firstChild.isFirstChild(true);
0941: firstChild.previousSibling = oldInternal.previousSibling;
0942: }
0943: } else {
0944: ChildNode prev = oldInternal.previousSibling;
0945: ChildNode next = oldInternal.nextSibling;
0946: prev.nextSibling = next;
0947: if (next == null) {
0948: // removing last child
0949: ChildNode firstChild = (ChildNode) value;
0950: firstChild.previousSibling = prev;
0951: } else {
0952: // removing some other child in the middle
0953: next.previousSibling = prev;
0954: }
0955: }
0956:
0957: // Save previous sibling for normalization checking.
0958: ChildNode oldPreviousSibling = oldInternal.previousSibling();
0959:
0960: // Remove oldInternal's references to tree
0961: oldInternal.ownerNode = ownerDocument;
0962: oldInternal.isOwned(false);
0963: oldInternal.nextSibling = null;
0964: oldInternal.previousSibling = null;
0965:
0966: changed();
0967:
0968: // notify document
0969: ownerDocument.removedNode(this , replace);
0970:
0971: checkNormalizationAfterRemove(oldPreviousSibling);
0972:
0973: return oldInternal;
0974:
0975: } // internalRemoveChild(Node,int):Node
0976:
0977: /**
0978: * Make newChild occupy the location that oldChild used to
0979: * have. Note that newChild will first be removed from its previous
0980: * parent, if any. Equivalent to inserting newChild before oldChild,
0981: * then removing oldChild.
0982: *
0983: * @return oldChild, in its new state (removed).
0984: *
0985: * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a
0986: * type that shouldn't be a child of this node, or if newChild is
0987: * one of our ancestors.
0988: *
0989: * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a
0990: * different owner document than we do.
0991: *
0992: * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of
0993: * this node.
0994: *
0995: * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is
0996: * read-only.
0997: */
0998: public Node replaceChild(Node newChild, Node oldChild)
0999: throws DOMException {
1000:
1001: makeChildNode();
1002:
1003: // If Mutation Events are being generated, this operation might
1004: // throw aggregate events twice when modifying an Attr -- once
1005: // on insertion and once on removal. DOM Level 2 does not specify
1006: // this as either desirable or undesirable, but hints that
1007: // aggregations should be issued only once per user request.
1008:
1009: // notify document
1010: CoreDocumentImpl ownerDocument = ownerDocument();
1011: ownerDocument.replacingNode(this );
1012:
1013: internalInsertBefore(newChild, oldChild, true);
1014: if (newChild != oldChild) {
1015: internalRemoveChild(oldChild, true);
1016: }
1017:
1018: // notify document
1019: ownerDocument.replacedNode(this );
1020:
1021: return oldChild;
1022: }
1023:
1024: //
1025: // NodeList methods
1026: //
1027:
1028: /**
1029: * NodeList method: Count the immediate children of this node
1030: * @return int
1031: */
1032: public int getLength() {
1033:
1034: if (hasStringValue()) {
1035: return 1;
1036: }
1037: ChildNode node = (ChildNode) value;
1038: int length = 0;
1039: for (; node != null; node = node.nextSibling) {
1040: length++;
1041: }
1042: return length;
1043:
1044: } // getLength():int
1045:
1046: /**
1047: * NodeList method: Return the Nth immediate child of this node, or
1048: * null if the index is out of bounds.
1049: * @return org.w3c.dom.Node
1050: * @param index int
1051: */
1052: public Node item(int index) {
1053:
1054: if (hasStringValue()) {
1055: if (index != 0 || value == null) {
1056: return null;
1057: } else {
1058: makeChildNode();
1059: return (Node) value;
1060: }
1061: }
1062: if (index < 0) {
1063: return null;
1064: }
1065: ChildNode node = (ChildNode) value;
1066: for (int i = 0; i < index && node != null; i++) {
1067: node = node.nextSibling;
1068: }
1069: return node;
1070:
1071: } // item(int):Node
1072:
1073: //
1074: // DOM3
1075: //
1076:
1077: /**
1078: * DOM Level 3 WD- Experimental.
1079: * Override inherited behavior from ParentNode to support deep equal.
1080: * isEqualNode is always deep on Attr nodes.
1081: */
1082: public boolean isEqualNode(Node arg) {
1083: return super .isEqualNode(arg);
1084: }
1085:
1086: /**
1087: * Introduced in DOM Level 3. <p>
1088: * Checks if a type is derived from another by restriction. See:
1089: * http://www.w3.org/TR/DOM-Level-3-Core/core.html#TypeInfo-isDerivedFrom
1090: *
1091: * @param typeNamespaceArg
1092: * The namspace of the ancestor type declaration
1093: * @param typeNameArg
1094: * The name of the ancestor type declaration
1095: * @param derivationMethod
1096: * The derivation method
1097: *
1098: * @return boolean True if the type is derived by restriciton for the
1099: * reference type
1100: */
1101: public boolean isDerivedFrom(String typeNamespaceArg,
1102: String typeNameArg, int derivationMethod) {
1103:
1104: return false;
1105: }
1106:
1107: //
1108: // Public methods
1109: //
1110:
1111: /**
1112: * Override default behavior so that if deep is true, children are also
1113: * toggled.
1114: * @see Node
1115: * <P>
1116: * Note: this will not change the state of an EntityReference or its
1117: * children, which are always read-only.
1118: */
1119: public void setReadOnly(boolean readOnly, boolean deep) {
1120:
1121: super .setReadOnly(readOnly, deep);
1122:
1123: if (deep) {
1124:
1125: if (needsSyncChildren()) {
1126: synchronizeChildren();
1127: }
1128:
1129: if (hasStringValue()) {
1130: return;
1131: }
1132: // Recursively set kids
1133: for (ChildNode mykid = (ChildNode) value; mykid != null; mykid = mykid.nextSibling) {
1134: if (mykid.getNodeType() != Node.ENTITY_REFERENCE_NODE) {
1135: mykid.setReadOnly(readOnly, true);
1136: }
1137: }
1138: }
1139: } // setReadOnly(boolean,boolean)
1140:
1141: //
1142: // Protected methods
1143: //
1144:
1145: /**
1146: * Override this method in subclass to hook in efficient
1147: * internal data structure.
1148: */
1149: protected void synchronizeChildren() {
1150: // By default just change the flag to avoid calling this method again
1151: needsSyncChildren(false);
1152: }
1153:
1154: /**
1155: * Checks the normalized state of this node after inserting a child.
1156: * If the inserted child causes this node to be unnormalized, then this
1157: * node is flagged accordingly.
1158: * The conditions for changing the normalized state are:
1159: * <ul>
1160: * <li>The inserted child is a text node and one of its adjacent siblings
1161: * is also a text node.
1162: * <li>The inserted child is is itself unnormalized.
1163: * </ul>
1164: *
1165: * @param insertedChild the child node that was inserted into this node
1166: *
1167: * @throws NullPointerException if the inserted child is <code>null</code>
1168: */
1169: void checkNormalizationAfterInsert(ChildNode insertedChild) {
1170: // See if insertion caused this node to be unnormalized.
1171: if (insertedChild.getNodeType() == Node.TEXT_NODE) {
1172: ChildNode prev = insertedChild.previousSibling();
1173: ChildNode next = insertedChild.nextSibling;
1174: // If an adjacent sibling of the new child is a text node,
1175: // flag this node as unnormalized.
1176: if ((prev != null && prev.getNodeType() == Node.TEXT_NODE)
1177: || (next != null && next.getNodeType() == Node.TEXT_NODE)) {
1178: isNormalized(false);
1179: }
1180: } else {
1181: // If the new child is not normalized,
1182: // then this node is inherently not normalized.
1183: if (!insertedChild.isNormalized()) {
1184: isNormalized(false);
1185: }
1186: }
1187: } // checkNormalizationAfterInsert(ChildNode)
1188:
1189: /**
1190: * Checks the normalized of this node after removing a child.
1191: * If the removed child causes this node to be unnormalized, then this
1192: * node is flagged accordingly.
1193: * The conditions for changing the normalized state are:
1194: * <ul>
1195: * <li>The removed child had two adjacent siblings that were text nodes.
1196: * </ul>
1197: *
1198: * @param previousSibling the previous sibling of the removed child, or
1199: * <code>null</code>
1200: */
1201: void checkNormalizationAfterRemove(ChildNode previousSibling) {
1202: // See if removal caused this node to be unnormalized.
1203: // If the adjacent siblings of the removed child were both text nodes,
1204: // flag this node as unnormalized.
1205: if (previousSibling != null
1206: && previousSibling.getNodeType() == Node.TEXT_NODE) {
1207:
1208: ChildNode next = previousSibling.nextSibling;
1209: if (next != null && next.getNodeType() == Node.TEXT_NODE) {
1210: isNormalized(false);
1211: }
1212: }
1213: } // checkNormalizationAfterRemove(ChildNode)
1214:
1215: //
1216: // Serialization methods
1217: //
1218:
1219: /** Serialize object. */
1220: private void writeObject(ObjectOutputStream out) throws IOException {
1221:
1222: // synchronize chilren
1223: if (needsSyncChildren()) {
1224: synchronizeChildren();
1225: }
1226: // write object
1227: out.defaultWriteObject();
1228:
1229: } // writeObject(ObjectOutputStream)
1230:
1231: /** Deserialize object. */
1232: private void readObject(ObjectInputStream ois)
1233: throws ClassNotFoundException, IOException {
1234:
1235: // perform default deseralization
1236: ois.defaultReadObject();
1237:
1238: // hardset synchildren - so we don't try to sync -
1239: // it does not make any sense to try to synchildren when we just
1240: // deserialize object.
1241: needsSyncChildren(false);
1242:
1243: } // readObject(ObjectInputStream)
1244:
1245: } // class AttrImpl
|