
com.sun.xml.tree.ParentNode Maven / Gradle / Ivy
/*
* $Id: ParentNode.java,v 1.9 1999/05/17 23:50:27 mode Exp $
*
* Copyright (c) 1998-1999 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
package com.sun.xml.tree;
import java.io.IOException;
import java.io.Writer;
import org.xml.sax.SAXException;
import org.w3c.dom.*;
/**
* This adds an implementation of "parent of" relationships to the NodeBase
* class. It implements operations for maintaining a set of children,
* providing indexed access to them, and writing them them out as text.
*
* The NodeList it implements to describe its children is "live", as
* required by DOM. That means that indexed accesses by applications must
* handle cases associated with unstable indices and lengths. Indices
* should not be stored if they can be invalidated by changes, including
* changes made by other threads.
*
* @author David Brownell
* @version $Revision: 1.9 $
*/
// not public ... javadoc looks a bit odd (hidden base class)
// but it's only subclassable within this package anyway
abstract class ParentNode
extends NodeBase
implements XmlReadable
{
private NodeBase children [];
private int length;
/**
* Builds a ParentNode, which can have children that are
* subclasses of NodeBase.
*/
// package private
ParentNode () { }
/**
* Called to minimize space utilization. Affects only
* this node; children must be individually trimmed.
*/
public void trimToSize ()
{
if (length == 0)
children = null;
else if (children.length != length) {
NodeBase temp [] = new NodeBase [length];
System.arraycopy (children, 0, temp, 0, length);
children = temp;
}
}
// package private
void reduceWaste ()
{
if (children == null)
return;
//
// Arbitrary -- rather than paying trimToSize() costs
// on most elements, we routinely accept some waste but
// do try to reduce egregious waste. Interacts with
// the array allocation done in appendChild.
//
if ((children.length - length) > 6)
trimToSize ();
}
/**
* Writes each of the child nodes. For element nodes, this adds
* whitespace to indent non-text children (it prettyprints) unless
* the xml:space='preserve' attribute applies, or the
* write context disables prettyprinting.
*
* @param context describes how the children should be printed
*/
public void writeChildrenXml (XmlWriteContext context) throws IOException
{
if (children == null)
return;
int oldIndent = 0;
boolean preserve = true;
boolean pureText = true;
if (getNodeType () == ELEMENT_NODE) {
preserve = "preserve".equals (
getInheritedAttribute ("xml:space"));
oldIndent = context.getIndentLevel ();
}
try {
if (!preserve)
context.setIndentLevel (oldIndent + 2);
for (int i = 0; i < length; i++) {
if (!preserve && children [i].getNodeType () != TEXT_NODE) {
context.printIndent ();
pureText = false;
}
children [i].writeXml (context);
}
} finally {
if (!preserve) {
context.setIndentLevel (oldIndent);
if (!pureText)
context.printIndent (); // for ETag
}
}
}
/**
* Subclasses may override this method, which is called shortly after
* the object type is known and before any children are processed.
* For elements, attributes are known and may be modified; since
* parent context is available, inherited attributes can be seen.
*
*
The default implementation does nothing.
*
* @param context provides location and error reporting data
*/
public void startParse (ParseContext context)
throws SAXException
{
// nothing
}
/**
* Subclasses may override this method, which is called after each
* child (text, element, processing instruction) is fully parsed.
* Subclassers may substitute, discard, reorder, modify, or otherwise
* process the child. For example, elements which correspond to object
* properties may be stored in that way, rather than appended. The
* startParse method has always been called before this.
*
*
The default implementation does nothing.
*
* @param child the child which has just been completely parsed;
* it is already a child of this node.
* @param context provides location and error reporting data
*/
public void doneChild (NodeEx child, ParseContext context)
throws SAXException
{
}
/**
* Subclasses may override this method, which is called shortly after
* the object is fully parsed. The startParse method has
* always been called before this, and doneChild will have been called
* for each child.
*
*
The default implementation does nothing.
*
* @param context provides location and error reporting data
*/
public void doneParse (ParseContext context)
throws SAXException
{
// nothing
}
// package private -- overridden in implementation classes
abstract void checkChildType (int type)
throws DOMException;
// DOM support
/**
* DOM: Returns true if there are children to this node.
*/
final public boolean hasChildNodes ()
{
return length > 0;
}
/**
* DOM: Returns the first child of this node, else null if there
* are no children.
*/
final public Node getFirstChild ()
{
if (length == 0)
return null;
return children [0];
}
/**
* DOM: Returns the last child of this node, else null if there
* are no children.
*/
final public Node getLastChild ()
{
if (length == 0)
return null;
return children [length - 1];
}
/** DOM: Returns the number of children */
final public int getLength ()
{
return length;
}
/** DOM: Returns the Nth child, or null */
final public Node item (int i)
{
if (length == 0 || i >= length)
return null;
try {
return children [i];
} catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}
// groups all the "wrong document/implementation" checks
private NodeBase checkDocument (Node newChild)
throws DOMException
{
if (newChild == null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
// check for wrong implementation
if (!(newChild instanceof NodeBase))
throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
Document owner = newChild.getOwnerDocument ();
XmlDocument myOwner = ownerDocument;
NodeBase child = (NodeBase) newChild;
// bizarre DOM special case for document
if (myOwner == null && this instanceof XmlDocument)
myOwner = (XmlDocument) this;
// check for wrong document
if (owner != null && owner != myOwner)
throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
// permit "unowned" NodeBase children to be added,
// e.g. if someone constructs an ElementNode directly
if (owner == null) {
child.setOwnerDocument (myOwner);
}
if (child.hasChildNodes ()) {
for (int i = 0; true; i++) {
Node node = child.item (i);
if (node == null)
break;
if (node.getOwnerDocument () == null)
((NodeBase)node).setOwnerDocument (myOwner);
else if (node.getOwnerDocument () != myOwner)
throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
}
}
return child;
}
// makes sure that child isn't an ancestor of this
private void checkNotAncestor (Node newChild) throws DOMException
{
// text, etc ...
if (!newChild.hasChildNodes ())
return;
Node ancestor = this;
while (ancestor != null) {
if (newChild == ancestor)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
ancestor = ancestor.getParentNode ();
}
}
// update mutation count
private void mutated ()
{
XmlDocument doc = ownerDocument;
if (doc == null && this instanceof XmlDocument)
doc = (XmlDocument) this;
if (doc != null)
doc.mutationCount++;
}
//
// When fragments are appended/inserted/replaced, their entire
// contents get moved and the fragment becomes empty.
//
private void consumeFragment (Node fragment, Node before)
throws DOMException
{
ParentNode frag = (ParentNode) fragment;
Node temp;
// don't start insertions we can't complete
for (int i = 0; (temp = frag.item (i)) != null; i++) {
checkNotAncestor (temp);
checkChildType (temp.getNodeType ());
}
while ((temp = frag.item (0)) != null)
insertBefore (temp, before);
}
/**
* DOM: Appends the child to the set of this node's children.
* The new child must belong to this document.
*
* @param newChild the new child to be appended
*/
public Node appendChild (Node newChild)
throws DOMException
{
NodeBase child;
if (readonly)
throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
child = checkDocument (newChild);
if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
consumeFragment (newChild, null);
return newChild;
}
checkNotAncestor (newChild);
checkChildType (child.getNodeType ());
// this is the only place this vector needs allocating,
// though it may also need to be grown in insertBefore.
// most elements have very few children
if (children == null)
children = new NodeBase [3];
else if (children.length == length) {
NodeBase temp [] = new NodeBase [length * 2];
System.arraycopy (children, 0, temp, 0, length);
children = temp;
}
child.setParentNode (this, length);
children [length++] = child;
mutated ();
return child;
}
/**
* DOM: Inserts the new child before the specified child,
* which if null indicates appending the new child to the
* current set of children. The new child must belong to
* this particular document.
*
* @param newChild the new child to be inserted
* @param refChild node before which newChild is to be inserted
*/
public Node insertBefore (Node newChild, Node refChild)
throws DOMException
{
NodeBase child;
if (readonly)
throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
if (refChild == null)
return appendChild (newChild);
if (length == 0)
throw new DomEx (DomEx.NOT_FOUND_ERR);
child = checkDocument (newChild);
if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
consumeFragment (newChild, refChild);
return newChild;
}
checkNotAncestor (newChild);
checkChildType (newChild.getNodeType ());
// grow array if needed
if (children.length == length) {
NodeBase temp [] = new NodeBase [length * 2];
System.arraycopy (children, 0, temp, 0, length);
children = temp;
}
for (int i = 0; i < length; i++) {
if (children [i] != refChild)
continue;
child.setParentNode (this, i);
System.arraycopy (children, i, children, i + 1, length - i);
children [i] = child;
length++;
mutated ();
return newChild;
}
throw new DomEx (DomEx.NOT_FOUND_ERR);
}
/**
* DOM: Replaces the specified child with the new node,
* returning the original child or throwing an exception.
* The new child must belong to this particular document.
*
* @param newChild the new child to be inserted
* @param refChild node which is to be replaced
*/
public Node replaceChild (Node newChild, Node refChild)
throws DOMException
{
NodeBase child;
if (readonly)
throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
if (newChild == null || refChild == null)
throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
if (children == null)
throw new DomEx (DomEx.NOT_FOUND_ERR);
child = checkDocument (newChild);
if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
consumeFragment (newChild, refChild);
return removeChild (refChild);
}
checkNotAncestor (newChild);
checkChildType (newChild.getNodeType ());
for (int i = 0; i < length; i++) {
if (children [i] != refChild)
continue;
child.setParentNode (this, i);
children [i] = child;
((NodeBase) refChild).setParentNode (null, -1);
mutated ();
return refChild;
}
throw new DomEx (DomEx.NOT_FOUND_ERR);
}
/**
* DOM: removes child if present, returning argument.
*
* @param oldChild the node which is to be removed
*/
public Node removeChild (Node oldChild)
throws DOMException
{
NodeBase child;
if (readonly)
throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
if (!(oldChild instanceof NodeBase))
throw new DomEx (DomEx.NOT_FOUND_ERR);
child = (NodeBase) oldChild;
for (int i = 0; i < length; i++) {
if (children [i] != child)
continue;
if ((i + 1) != length)
System.arraycopy (children, i + 1, children, i,
(length - 1) - i);
length--;
children [length] = null;
child.setParentNode (null, -1);
mutated ();
return oldChild;
}
throw new DomEx (DomEx.NOT_FOUND_ERR);
}
/**
* DOM: Returns a "live" list view of the elements below this
* one which have the specified tag name. Because this is "live", this
* API is dangerous -- indices are not stable in the face of most tree
* updates. Use a TreeWalker instead.
*
* @param tagname the tag name to show; or "*" for all elements.
* @return list of such elements
*/
public NodeList getElementsByTagName (String tagname)
{
if ("*".equals (tagname))
tagname = null;
return new TagList (tagname);
}
//
// Slightly optimized to track document mutation count. For now
// we assume that a 32 bit counter won't wrap around, and that
// there's no point in caching list length.
//
class TagList implements NodeList
{
private String tag;
private int lastMutationCount;
private int lastIndex;
private TreeWalker lastWalker;
private int getLastMutationCount ()
{
XmlDocument doc = (XmlDocument) getOwnerDocument ();
return (doc == null) ? 0 : doc.mutationCount;
}
TagList (String tag) { this.tag = tag; }
public Node item (int i)
{
if (i < 0)
return null;
int temp = getLastMutationCount ();
// Can we try to reuse the last walker?
if (lastWalker != null) {
if (i < lastIndex || temp != lastMutationCount)
lastWalker = null;
}
// if not, get a new one ...
if (lastWalker == null) {
lastWalker = new TreeWalker (ParentNode.this);
lastIndex = -1;
lastMutationCount = temp;
}
if (i == lastIndex)
return lastWalker.getCurrent ();
Node node = null;
while (i > lastIndex
&& (node = lastWalker.getNextElement (tag)) != null)
lastIndex++;
return node;
}
public int getLength ()
{
TreeWalker walker = new TreeWalker (ParentNode.this);
Node node = null;
int retval;
for (retval = 0;
(node = walker.getNextElement (tag)) != null;
retval++)
continue;
return retval;
}
}
/**
* Returns the index of the node in the list of children, such
* that item() will return that child.
*
* @param maybeChild the node which may be a child of this one
* @return the index of the node in the set of children, or
* else -1 if that node is not a child
*/
final public int getIndexOf (Node maybeChild)
{
for (int i = 0; i < length; i++)
if (children [i] == maybeChild)
return i;
return -1;
}
}