Clover coverage report - Clover results for XOM 1.2d1
Coverage timestamp: Wed Feb 8 2006 08:31:33 EST
file stats: LOC: 461   Methods: 18
NCLOC: 172   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Document.java 100% 100% 100% 100%
coverage
 1    /* Copyright 2002-2004 Elliotte Rusty Harold
 2   
 3    This library is free software; you can redistribute it and/or modify
 4    it under the terms of version 2.1 of the GNU Lesser General Public
 5    License as published by the Free Software Foundation.
 6   
 7    This library is distributed in the hope that it will be useful,
 8    but WITHOUT ANY WARRANTY; without even the implied warranty of
 9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 10    GNU Lesser General Public License for more details.
 11   
 12    You should have received a copy of the GNU Lesser General Public
 13    License along with this library; if not, write to the
 14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 15    Boston, MA 02111-1307 USA
 16   
 17    You can contact Elliotte Rusty Harold by sending e-mail to
 18    elharo@metalab.unc.edu. Please include the word "XOM" in the
 19    subject line. The XOM home page is located at http://www.xom.nu/
 20    */
 21   
 22    package nu.xom;
 23   
 24    /**
 25    * <p>
 26    * The <code>Document</code> class represents
 27    * a complete XML document including its root element,
 28    * prolog, and epilog.
 29    * </p>
 30    *
 31    * @author Elliotte Rusty Harold
 32    * @version 1.1b5
 33    *
 34    */
 35    public class Document extends ParentNode {
 36   
 37    /**
 38    * <p>
 39    * Creates a new <code>Document</code> object with the
 40    * specified root element.
 41    * </p>
 42    *
 43    * @param root the root element of this document
 44    *
 45    * @throws NullPointerException if <code>root</code> is null
 46    * @throws MultipleParentException if <code>root</code> already
 47    * has a parent
 48    */
 49  40072 public Document(Element root) {
 50  40072 _insertChild(root, 0);
 51    }
 52   
 53   
 54    /**
 55    * <p>
 56    * Creates a copy of this document.
 57    * </p>
 58    *
 59    * @param doc the document to copy
 60    *
 61    * @throws NullPointerException if <code>doc</code> is null
 62    */
 63  143 public Document(Document doc) {
 64   
 65  143 insertChild(doc.getRootElement().copy(), 0);
 66  142 int count = doc.getChildCount();
 67  142 for (int i = 0; i < count; i++) {
 68  163 Node child = doc.getChild(i);
 69  163 if (!(child.isElement())) {
 70  21 this.insertChild(child.copy(), i);
 71    }
 72    }
 73  142 this.actualBaseURI = doc.actualBaseURI;
 74   
 75    }
 76   
 77   
 78  41831 final void insertionAllowed(Node child, int position) {
 79   
 80  41831 if (child == null) {
 81  1 throw new NullPointerException(
 82    "Tried to insert a null child in the document");
 83    }
 84  41830 else if (child.getParent() != null) {
 85  1 throw new MultipleParentException("Child already has a parent.");
 86    }
 87  41829 else if (child.isComment() || child.isProcessingInstruction()) {
 88  1527 return;
 89    }
 90  40302 else if (child.isDocType()) {
 91  77 if (position <= getRootPosition()) {
 92  75 DocType oldDocType = getDocType();
 93  75 if (oldDocType != null) {
 94  2 throw new IllegalAddException(
 95    "Tried to insert a second DOCTYPE"
 96    );
 97    }
 98  73 return;
 99    }
 100    else {
 101  2 throw new IllegalAddException(
 102    "Cannot add a document type declaration "
 103    + "after the root element"
 104    );
 105    }
 106    }
 107  40225 else if (child.isElement()) {
 108  40213 if (getChildCount() == 0) return;
 109    else {
 110  4 throw new IllegalAddException(
 111    "Cannot add a second root element to a Document."
 112    );
 113    }
 114    }
 115    else {
 116  8 throw new IllegalAddException("Cannot add a "
 117    + child.getClass().getName() + " to a Document.");
 118    }
 119   
 120    }
 121   
 122   
 123  98 private int getRootPosition() {
 124   
 125    // This looks like an infinite loop but it isn't
 126    // because all documents have root elements
 127  98 for (int i = 0; ; i++) {
 128  133 Node child = getChild(i);
 129  133 if (child.isElement()) {
 130  98 return i;
 131    }
 132    }
 133   
 134    }
 135   
 136   
 137    /**
 138    * <p>
 139    * Returns this document's document type declaration,
 140    * or null if it doesn't have one.
 141    * </p>
 142    *
 143    * @return the document type declaration
 144    *
 145    * @see #setDocType
 146    *
 147    */
 148  166 public final DocType getDocType() {
 149   
 150  166 for (int i = 0; i < getChildCount(); i++) {
 151  186 Node child = getChild(i);
 152  186 if (child.isDocType()) {
 153  65 return (DocType) child;
 154    }
 155    }
 156  101 return null;
 157   
 158    }
 159   
 160   
 161    /**
 162    * <p>
 163    * Sets this document's document type declaration.
 164    * If this document already has a document type declaration,
 165    * then it's inserted at that position. Otherwise, it's inserted
 166    * at the beginning of the document.
 167    * </p>
 168    *
 169    * @param doctype the document type declaration
 170    *
 171    * @throws MultipleParentException if <code>doctype</code> belongs
 172    * to another document
 173    * @throws NullPointerException if <code>doctype</code> is null
 174    *
 175    */
 176  17 public void setDocType(DocType doctype) {
 177   
 178  17 DocType oldDocType = getDocType();
 179  17 if (doctype == null) {
 180  1 throw new NullPointerException("Null DocType");
 181    }
 182  1 else if (doctype == oldDocType) return;
 183  15 else if (doctype.getParent() != null) {
 184  2 throw new MultipleParentException("DocType belongs to another document");
 185    }
 186   
 187  10 if (oldDocType == null) insertChild(doctype, 0);
 188    else {
 189  3 int position = indexOf(oldDocType);
 190  3 super.removeChild(position);
 191  3 fastInsertChild(doctype, position);
 192  3 oldDocType.setParent(null);
 193  3 doctype.setParent(this);
 194    }
 195   
 196    }
 197   
 198   
 199    /**
 200    * <p>
 201    * Returns this document's root element.
 202    * This is guaranteed to be non-null.
 203    * </p>
 204    *
 205    * @return the root element
 206    */
 207  62795 public final Element getRootElement() {
 208   
 209    // This looks like an infinite loop but it isn't because
 210    // all documents have root elements.
 211  62795 for (int i = 0; ; i++) {
 212  71301 Node child = getChild(i);
 213  71301 if (child.isElement()) {
 214  62795 return (Element) child;
 215    }
 216    }
 217   
 218    }
 219   
 220   
 221    /**
 222    * <p>
 223    * Replaces the current root element with a different root element.
 224    * </p>
 225    *
 226    * @param root the new root element
 227    *
 228    * @throws MultipleParentException if root has a parent
 229    * @throws NullPointerException if root is null
 230    */
 231  44039 public void setRootElement(Element root) {
 232   
 233  44039 Element oldRoot = this.getRootElement();
 234  21770 if (root == oldRoot) return;
 235  22269 else if (root == null) {
 236  1 throw new NullPointerException("Root element cannot be null");
 237    }
 238  22268 else if (root.getParent() != null) {
 239  2 throw new MultipleParentException(root.getQualifiedName()
 240    + " already has a parent");
 241    }
 242   
 243  22266 fillInBaseURI(oldRoot);
 244  22266 int index = indexOf(oldRoot);
 245   
 246  22266 oldRoot.setParent(null);
 247  22266 children[index] = root;
 248  22266 root.setParent(this);
 249   
 250    }
 251   
 252   
 253    /**
 254    * <p>
 255    * Sets the URI from which this document was loaded, and
 256    * against which relative URLs in this document will be resolved.
 257    * Setting the base URI to null or the empty string removes any
 258    * existing base URI.
 259    * </p>
 260    *
 261    * @param URI the base URI of this document
 262    *
 263    * @throws MalformedURIException if <code>URI</code> is
 264    * not a legal absolute URI
 265    */
 266  39748 public void setBaseURI(String URI) {
 267  39748 setActualBaseURI(URI);
 268    }
 269   
 270   
 271    /**
 272    * <p>
 273    * Returns the absolute URI from which this document was loaded.
 274    * This method returns the empty string if the base URI is not
 275    * known; for instance if the document was created in memory with
 276    * a constructor rather than by parsing an existing document.
 277    * </p>
 278    *
 279    * @return the base URI of this document
 280    */
 281  24714 public final String getBaseURI() {
 282  24714 return getActualBaseURI();
 283    }
 284   
 285   
 286    /**
 287    * <p>
 288    * Removes the child of this document at the specified position.
 289    * Indexes begin at 0 and count up to one less than the number
 290    * of children of this document. The root element cannot be
 291    * removed. Instead, use <code>setRootElement</code> to replace
 292    * the existing root element with a different element.
 293    * </p>
 294    *
 295    * @param position index of the node to remove
 296    *
 297    * @return the node which was removed
 298    *
 299    * @throws IndexOutOfBoundsException if the index is negative or
 300    * greater than the number of children of this document - 1
 301    * @throws WellformednessException if the index points
 302    * to the root element
 303    */
 304  21 public Node removeChild(int position) {
 305   
 306  21 if (position == getRootPosition()) {
 307  2 throw new WellformednessException(
 308    "Cannot remove the root element"
 309    );
 310    }
 311  19 return super.removeChild(position);
 312   
 313    }
 314   
 315   
 316    /**
 317    * <p>
 318    * Removes the specified child from this document.
 319    * The root element cannot be removed.
 320    * Instead, use <code>setRootElement</code> to replace the
 321    * existing root element with a different element.
 322    * </p>
 323    *
 324    * @param child node to remove
 325    *
 326    * @return the node which was removed
 327    *
 328    * @throws NoSuchChildException if the node is not a
 329    * child of this node
 330    * @throws WellformednessException if child is the root element
 331    */
 332  19 public Node removeChild(Node child) {
 333   
 334  19 if (child == getRootElement()) {
 335  2 throw new WellformednessException(
 336    "Cannot remove the root element");
 337    }
 338  17 return super.removeChild(child);
 339   
 340    }
 341   
 342   
 343    /**
 344    * <p>
 345    * Replaces an existing child with a new child node.
 346    * If <code>oldChild</code> is not a child of this node,
 347    * then a <code>NoSuchChildException</code> is thrown.
 348    * The root element can only be replaced by another element.
 349    * </p>
 350    *
 351    * @param oldChild the node removed from the tree
 352    * @param newChild the node inserted into the tree
 353    *
 354    * @throws MultipleParentException if <code>newChild</code> already
 355    * has a parent
 356    * @throws NoSuchChildException if <code>oldChild</code>
 357    * is not a child of this node
 358    * @throws NullPointerException if either argument is null
 359    * @throws IllegalAddException if <code>newChild</code> is an
 360    * attribute or a text node
 361    * @throws WellformednessException if <code>newChild</code>
 362    * <code>oldChild</code> is an element and
 363    * <code>newChild</code> is not
 364    */
 365  7 public void replaceChild(Node oldChild, Node newChild) {
 366   
 367  7 if (oldChild == getRootElement()
 368    && newChild != null && newChild.isElement()) {
 369  2 setRootElement((Element) newChild);
 370    }
 371  5 else if (oldChild == getDocType()
 372    && newChild != null && newChild.isDocType()) {
 373  2 setDocType((DocType) newChild);
 374    }
 375    else {
 376  3 super.replaceChild(oldChild, newChild);
 377    }
 378   
 379    }
 380   
 381   
 382    /**
 383    * <p>
 384    * Returns the value of the document as defined by XPath 1.0.
 385    * This is the same as the value of the root element, which
 386    * is the complete PCDATA content of the root element, without
 387    * any tags, comments, or processing instructions after all
 388    * entity and character references have been resolved.
 389    * </p>
 390    *
 391    * @return value of the root element of this document
 392    *
 393    */
 394  12 public final String getValue() {
 395  12 return getRootElement().getValue();
 396    }
 397   
 398   
 399    /**
 400    * <p>
 401    * Returns the actual complete, well-formed XML document as a
 402    * <code>String</code>. Significant white space is preserved.
 403    * Insignificant white space in tags, the prolog, the epilog,
 404    * and the internal DTD subset is not preserved.
 405    * Entity and character references are not preserved.
 406    * The entire document is contained in this one string.
 407    * </p>
 408    *
 409    * @return a string containing this entire XML document
 410    */
 411  49 public final String toXML() {
 412   
 413  49 StringBuffer result = new StringBuffer();
 414   
 415    // XML declaration
 416  49 result.append("<?xml version=\"1.0\"?>\n");
 417   
 418    // children
 419  49 for (int i = 0; i < getChildCount(); i++) {
 420  66 result.append(getChild(i).toXML());
 421  66 result.append("\n");
 422    }
 423   
 424  49 return result.toString();
 425   
 426    }
 427   
 428   
 429    /**
 430    * <p>
 431    * Returns a complete copy of this document.
 432    * </p>
 433    *
 434    * @return a deep copy of this <code>Document</code> object
 435    */
 436  6 public Node copy() {
 437  6 return new Document(this);
 438    }
 439   
 440   
 441  247256 boolean isDocument() {
 442  247256 return true;
 443    }
 444   
 445   
 446    /**
 447    * <p>
 448    * Returns a string representation of this document suitable
 449    * for debugging and diagnosis. This is <em>not</em>
 450    * the XML representation of this document.
 451    * </p>
 452    *
 453    * @return a non-XML string representation of this document
 454    */
 455  1 public final String toString() {
 456  1 return "[" + getClass().getName() + ": "
 457    + getRootElement().getQualifiedName() + "]";
 458    }
 459   
 460   
 461    }