Clover coverage report - DrJava Test Coverage (drjava-20120422-r5456)
Coverage timestamp: Sun Apr 22 2012 03:13:25 CDT
file stats: LOC: 783   Methods: 41
NCLOC: 464   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
XMLConfig.java 93.7% 94.8% 95.1% 94.5%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (javaplt@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.util;
 38   
 39    import org.w3c.dom.*;
 40    import org.xml.sax.InputSource;
 41   
 42    import javax.xml.parsers.DocumentBuilder;
 43    import javax.xml.parsers.DocumentBuilderFactory;
 44    import javax.xml.parsers.ParserConfigurationException;
 45    import javax.xml.transform.*;
 46    import javax.xml.transform.dom.DOMSource;
 47    import javax.xml.transform.stream.StreamResult;
 48    import java.io.*;
 49    import java.util.*;
 50   
 51    /**
 52    * XML configuration management.
 53    * <p/>
 54    * This class uses DOM paths of a specific form to refer to nodes in the XML document.
 55    * Consider this XML structure:
 56    * <foo a="foo.a">
 57    * <bar>abc</bar>
 58    * <fum fee="xyz">def</fum>
 59    * </foo>
 60    * The path "foo/bar" refers to the value "abc".
 61    * The path "foo/fum" refers to the value "def".
 62    * If this form is used, there may be only #text or #comment nodes in the node. All #text nodes will be
 63    * concatenated and then stripped of whitespace at the beginning and the end.
 64    * The path "foo/fum.fee" refers to the value "xyz".
 65    * The path "foo.a" refers to the value "foo.a".
 66    *
 67    * When using getMultiple, any node or attribute name can be substituted with "*" to get all elements:
 68    * The path "foo/*" returns both the value "abc" and "def".
 69    * @author Mathias Ricken
 70    */
 71    public class XMLConfig {
 72    /** Newline string.
 73    */
 74    public static final String NL = System.getProperty("line.separator");
 75   
 76    /** XML document.
 77    */
 78    private Document _document;
 79   
 80    /** XMLConfig to delegate to, or null.
 81    */
 82    private XMLConfig _parent = null;
 83   
 84    /** Node where this XMLConfig starts if delegation is used, or null.
 85    */
 86    private Node _startNode = null;
 87   
 88    /** Creates an empty configuration.
 89    */
 90  17 public XMLConfig() {
 91  17 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 92  17 try {
 93  17 DocumentBuilder builder = factory.newDocumentBuilder();
 94  17 _document = builder.newDocument(); // Create from whole cloth
 95    // NOTE: not 1.4 compatible -- _document.setXmlStandalone(true);
 96    }
 97    catch(ParserConfigurationException e) {
 98  0 e.printStackTrace();
 99    }
 100    }
 101   
 102    /** Creates a configuration from an input stream.
 103    * @param is input stream
 104    */
 105  0 public XMLConfig(InputStream is) {
 106  0 init(new InputSource(is));
 107    }
 108   
 109    /** Creates a configuration from a reader.
 110    * @param r reader
 111    */
 112  31 public XMLConfig(Reader r) {
 113  31 init(new InputSource(r));
 114    }
 115   
 116    /** Creates a configuration that is a part of another configuration, starting at the specified node.
 117    * @param parent the configuration that contains this part
 118    * @param node the node in the parent configuration where this part starts
 119    */
 120  24 public XMLConfig(XMLConfig parent, Node node) {
 121  2 if ((parent == null) || (node == null)) { throw new XMLConfigException("Error in ctor: parent or node is null"); }
 122  22 _parent = parent;
 123  22 _startNode = node;
 124  22 _document = null;
 125    }
 126   
 127    /** Initialize this XML configuration.
 128    * @param is the XML input source
 129    */
 130  37 private void init(InputSource is) {
 131  37 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 132  37 DocumentBuilder builder = null;
 133  37 try {
 134  37 builder = factory.newDocumentBuilder();
 135  37 _document = builder.parse(is);
 136    // NOTE: not 1.4 compatible -- _document.setXmlStandalone(true);
 137    }
 138    catch(Exception e) {
 139  0 throw new XMLConfigException("Error in ctor", e);
 140    }
 141  37 _document.normalize();
 142    }
 143   
 144    /** Creates a configuration from a file.
 145    * @param f file
 146    */
 147  5 public XMLConfig(File f) {
 148  5 try {
 149  5 init(new InputSource(new FileInputStream(f)));
 150    }
 151    catch(FileNotFoundException e) {
 152  1 throw new XMLConfigException("Error in ctor", e);
 153    }
 154    }
 155   
 156    /** Creates a configuration from a file name.
 157    * @param filename file name
 158    */
 159  4 public XMLConfig(String filename) {
 160  4 try {
 161  4 init(new InputSource(new FileInputStream(filename)));
 162    }
 163    catch(FileNotFoundException e) {
 164  2 throw new XMLConfigException("Error in ctor", e);
 165    }
 166    }
 167   
 168  719 public boolean isDelegated() { return (_parent != null); }
 169   
 170    /** Saves configuration to an output stream
 171    * @param os output stream
 172    */
 173  68 public void save(OutputStream os) {
 174  22 if (isDelegated()) { _parent.save(os); return; }
 175   
 176    // Prepare the DOM document for writing
 177  46 Source source = new DOMSource(_document);
 178    /*
 179    // Prepare the output file
 180    Result result = new StreamResult(os);
 181    */
 182    // Write the DOM document to the file
 183  46 try {
 184  46 TransformerFactory tf = TransformerFactory.newInstance();
 185  46 tf.setAttribute("indent-number", Integer.valueOf(2));
 186  46 Transformer t = tf.newTransformer();
 187  46 t.setOutputProperty(OutputKeys.INDENT, "yes");
 188  46 t.transform(source, new StreamResult(new OutputStreamWriter(os, "utf-8")));
 189    /*
 190    Transformer xformer = TransformerFactory.newInstance().newTransformer();
 191    xformer.setOutputProperty(OutputKeys.INDENT, "yes");
 192    xformer.transform(source, result);
 193    */
 194    }
 195    catch(TransformerException e) {
 196  0 throw new XMLConfigException("Error in save", e);
 197    }
 198    catch(UnsupportedEncodingException e) {
 199  0 throw new XMLConfigException("Error in save", e);
 200    }
 201    }
 202   
 203    /** Saves configuration to a file.
 204    * @param f file
 205    */
 206  4 public void save(File f) {
 207  0 if (isDelegated()) { _parent.save(f); return; }
 208  4 FileOutputStream fos = null;
 209  4 try {
 210  4 fos = new FileOutputStream(f);
 211  3 save(fos);
 212    }
 213    catch(FileNotFoundException e) {
 214  1 throw new XMLConfigException("Error in save", e);
 215    }
 216    finally {
 217  4 try {
 218  3 if (fos != null) fos.close();
 219    }
 220    catch(IOException ioe) { /* ignore exception when closing */ }
 221    }
 222    }
 223   
 224    /** Saves configuration to a file specified by a file name.
 225    * @param filename file name
 226    */
 227  1 public void save(String filename) {
 228  1 save(new File(filename));
 229    }
 230   
 231    // ----- String ------
 232   
 233    /** Returns the value as specified by the DOM path.
 234    * @param path DOM path
 235    * @return value.
 236    */
 237  84 public String get(String path) {
 238  84 List<String> r = getMultiple(path);
 239  20 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 240  62 return r.get(0);
 241    }
 242   
 243    /** Returns the value as specified by the DOM path.
 244    * @param path DOM path
 245    * @param root node where the search should start
 246    * @return value.
 247    */
 248  98 public String get(String path, Node root) {
 249  98 List<String> r = getMultiple(path, root);
 250  4 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 251  94 return r.get(0);
 252    }
 253   
 254    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 255    * @param path DOM path
 256    * @param defaultVal default value in case value is not in DOM
 257    * @return value.
 258    */
 259  8 public String get(String path, String defaultVal) {
 260  8 try {
 261  8 return get(path);
 262    }
 263    catch(XMLConfigException e) {
 264  4 return defaultVal;
 265    }
 266    }
 267   
 268    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 269    * @param path DOM path
 270    * @param root node where the search should start
 271    * @param defaultVal default value in case value is not in DOM
 272    * @return value.
 273    */
 274  8 public String get(String path, Node root, String defaultVal) {
 275  8 try {
 276  8 return get(path, root);
 277    }
 278    catch(XMLConfigException e) {
 279  4 return defaultVal;
 280    }
 281    }
 282   
 283    // ----- Integer ------
 284   
 285    /** Returns the value as specified by the DOM path.
 286    * @param path DOM path
 287    * @return value.
 288    * @throws IllegalArgumentException
 289    */
 290  5 public int getInt(String path) {
 291  5 List<String> r = getMultiple(path);
 292  2 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 293  3 try {
 294  3 return Integer.valueOf(r.get(0));
 295    }
 296  1 catch(NumberFormatException nfe) { throw new IllegalArgumentException("Not an integer value.", nfe); }
 297    }
 298   
 299    /** Returns the value as specified by the DOM path.
 300    * @param path DOM path
 301    * @param root node where the search should start
 302    * @return value.
 303    * @throws IllegalArgumentException
 304    */
 305  112 public int getInt(String path, Node root) {
 306  112 List<String> r = getMultiple(path, root);
 307  2 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 308  110 try {
 309  110 return Integer.valueOf(r.get(0));
 310    }
 311  1 catch(NumberFormatException nfe) { throw new IllegalArgumentException("Not an integer value.", nfe); }
 312    }
 313   
 314    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 315    * @param path DOM path
 316    * @param defaultVal default value in case value is not in DOM
 317    * @return value.
 318    * @throws IllegalArgumentException
 319    */
 320  2 public int getInt(String path, int defaultVal) {
 321  2 try {
 322  2 return getInt(path);
 323    }
 324    catch(XMLConfigException e) {
 325  1 return defaultVal;
 326    }
 327    }
 328   
 329    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 330    * @param path DOM path
 331    * @param root node where the search should start
 332    * @param defaultVal default value in case value is not in DOM
 333    * @return value.
 334    * @throws IllegalArgumentException
 335    */
 336  2 public int getInt(String path, Node root, int defaultVal) {
 337  2 try {
 338  2 return getInt(path, root);
 339    }
 340    catch(XMLConfigException e) {
 341  1 return defaultVal;
 342    }
 343    }
 344   
 345    // ----- Boolean ------
 346   
 347    /** Returns the value as specified by the DOM path.
 348    * @param path DOM path
 349    * @return value.
 350    * @throws IllegalArgumentException
 351    */
 352  8 public boolean getBool(String path) {
 353  8 List<String> r = getMultiple(path);
 354  2 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 355  6 String s = r.get(0).toLowerCase().trim();
 356  6 if ((s.equals("true")) ||
 357    (s.equals("yes")) ||
 358  3 (s.equals("on"))) return true;
 359  3 if ((s.equals("false")) ||
 360    (s.equals("no")) ||
 361  2 (s.equals("off"))) return false;
 362  1 throw new IllegalArgumentException("Not a Boolean vlaue.");
 363    }
 364   
 365    /** Returns the value as specified by the DOM path.
 366    * @param path DOM path
 367    * @param root node where the search should start
 368    * @return value.
 369    * @throws IllegalArgumentException
 370    */
 371  39 public boolean getBool(String path, Node root) {
 372  39 List<String> r = getMultiple(path, root);
 373  24 if (r.size() != 1) throw new XMLConfigException("Number of results != 1");
 374  15 String s = r.get(0).toLowerCase().trim();
 375  15 if ((s.equals("true")) ||
 376    (s.equals("yes")) ||
 377  10 (s.equals("on"))) return true;
 378  5 if ((s.equals("false")) ||
 379    (s.equals("no")) ||
 380  4 (s.equals("off"))) return false;
 381  1 throw new IllegalArgumentException("Not a Boolean vlaue.");
 382   
 383    }
 384   
 385    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 386    * @param path DOM path
 387    * @param defaultVal default value in case value is not in DOM
 388    * @return value.
 389    * @throws IllegalArgumentException
 390    */
 391  3 public boolean getBool(String path, boolean defaultVal) {
 392  3 try {
 393  3 return getBool(path);
 394    }
 395    catch(XMLConfigException e) {
 396  2 return defaultVal;
 397    }
 398    }
 399   
 400    /** Returns the value as specified by the DOM path, or the default value if the value could not be found.
 401    * @param path DOM path
 402    * @param root node where the search should start
 403    * @param defaultVal default value in case value is not in DOM
 404    * @return value.
 405    * @throws IllegalArgumentException
 406    */
 407  6 public boolean getBool(String path, Node root, boolean defaultVal) {
 408  6 try {
 409  6 return getBool(path, root);
 410    }
 411    catch(XMLConfigException e) {
 412  3 return defaultVal;
 413    }
 414    }
 415   
 416    // ----- Other -----
 417   
 418    /** Returns the value as specified by the DOM path.
 419    * @param path DOM path
 420    * @return list of values.
 421    */
 422  124 public List<String> getMultiple(String path) {
 423  66 if (isDelegated()) { return getMultiple(path, _startNode); }
 424   
 425  58 return getMultiple(path, _document);
 426    }
 427   
 428    /** Returns the value as specified by the DOM path.
 429    * @param path DOM path
 430    * @param root node where the search should start
 431    * @return list of values.
 432    */
 433  373 public List<String> getMultiple(String path, Node root) {
 434  373 List<Node> accum = getNodes(path, root);
 435  373 List<String> strings = new LinkedList<String>();
 436  373 for(Node n: accum) {
 437  361 if (n instanceof Attr) {
 438  308 strings.add(n.getNodeValue());
 439    }
 440    else {
 441  53 Node child;
 442  53 String acc = "";
 443  53 child = n.getFirstChild();
 444  53 while(child != null) {
 445  50 if (child.getNodeName().equals("#text")) {
 446  47 acc += " " + child.getNodeValue();
 447    }
 448  3 else if (child.getNodeName().equals("#comment")) {
 449    // ignore
 450    }
 451    else {
 452  2 throw new XMLConfigException("Node " + n.getNodeName() + " contained node " + child.getNodeName() + ", but should only contain #text and #comment.");
 453    }
 454  48 child = child.getNextSibling();
 455    }
 456  51 strings.add(acc.trim());
 457    }
 458    }
 459  371 return strings;
 460    }
 461   
 462    /** Returns the nodes as specified by the DOM path.
 463    * @param path DOM path
 464    * @return list of nodes.
 465    */
 466  76 public List<Node> getNodes(String path) {
 467  36 if (isDelegated()) { return getNodes(path, _startNode); }
 468   
 469  40 return getNodes(path, _document);
 470    }
 471   
 472    /** Returns the nodes as specified by the DOM path.
 473    * @param path DOM path
 474    * @param root node where the search should start
 475    * @return list of nodes.
 476    */
 477  449 public List<Node> getNodes(String path, Node root) {
 478  449 List<Node> accum = new LinkedList<Node>();
 479  449 getMultipleHelper(path, root, accum, false);
 480  447 return accum;
 481    }
 482   
 483    /** Returns the value as specified by the DOM path.
 484    * @param path DOM path
 485    * @param n node where the search begins
 486    * @param accum accumulator
 487    * @param dotRead whether a dot has been read
 488    */
 489  659 private void getMultipleHelper(String path, Node n, List<Node> accum, boolean dotRead) {
 490  659 int dotPos = path.indexOf('.');
 491  659 boolean initialDot = (dotPos == 0);
 492  659 if ((path.length() > 0) && (dotPos == -1) && (!path.endsWith("/"))) {
 493  121 path = path + "/";
 494    }
 495  659 int slashPos = path.indexOf('/');
 496   
 497  659 if(dotPos != -1 && path.indexOf('.', dotPos+1) != -1)
 498  1 throw new XMLConfigException("An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)");
 499   
 500  658 if(dotPos != -1 && path.indexOf('/', dotPos+1) != -1)
 501  1 throw new XMLConfigException("An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)");
 502   
 503  657 if (((slashPos > -1) || (dotPos > -1)) && !dotRead || initialDot) {
 504  526 String nodeName;
 505  526 if ((slashPos > -1) && ((dotPos == -1) || (slashPos < dotPos))) {
 506  203 nodeName = path.substring(0, slashPos);
 507  203 path = path.substring(slashPos+1);
 508    }
 509    else {
 510  323 if (slashPos > -1) {
 511  0 throw new XMLConfigException("An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)");
 512    }
 513  323 if (!initialDot) {
 514  163 nodeName = path.substring(0, dotPos);
 515  163 path = path.substring(dotPos+1);
 516  163 dotRead = true;
 517    }
 518    else {
 519  160 path = path.substring(1);
 520  160 getMultipleAddAttributesHelper(path, n, accum);
 521  160 return;
 522    }
 523    }
 524  366 Node child = n.getFirstChild();
 525  366 if (nodeName.equals("*")) {
 526  12 while(child != null) {
 527  66 if (!child.getNodeName().equals("#text") && !child.getNodeName().equals("#comment")) {
 528  30 if (dotRead) {
 529  18 getMultipleAddAttributesHelper(path, child, accum);
 530    }
 531    else {
 532  12 getMultipleHelper(path, child, accum, false);
 533    }
 534    }
 535  66 child = child.getNextSibling();
 536    }
 537  12 return;
 538    }
 539    else {
 540  354 while(child != null) {
 541  1983 if (child.getNodeName().equals(nodeName)) {
 542    // found
 543  356 if (dotRead) {
 544  158 getMultipleAddAttributesHelper(path, child, accum);
 545    }
 546    else {
 547  198 getMultipleHelper(path, child, accum, false);
 548    }
 549    }
 550  1983 child = child.getNextSibling();
 551    }
 552  354 return;
 553    }
 554    }
 555    else {
 556  131 accum.add(n);
 557    }
 558    }
 559   
 560  336 private void getMultipleAddAttributesHelper(String path, Node n, List<Node> accum) {
 561  336 if ((path.indexOf('.') > -1) || (path.indexOf('/') > -1)) {
 562  0 throw new XMLConfigException("An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)");
 563    }
 564  336 NamedNodeMap attrMap = n.getAttributes();
 565  336 if (path.equals("*")) {
 566  16 for(int i = 0; i < attrMap.getLength(); ++i) {
 567  30 Node attr = attrMap.item(i);
 568  30 accum.add(attr);
 569    }
 570    }
 571    else {
 572  320 Node attr = attrMap.getNamedItem(path);
 573  320 if (attr != null) {
 574  278 accum.add(attr);
 575    }
 576    }
 577    }
 578   
 579    /** Set the value of the node or attribute specified by the DOM path.
 580    * @param path DOM path
 581    * @param value node or attribute value
 582    * @return the node that was created, or the parent node of the attribute if it was an attribute
 583    */
 584  41 public Node set(String path, String value) {
 585  4 if (isDelegated()) { return set(path, value, _startNode, true); }
 586   
 587  37 return set(path, value, _document, true);
 588    }
 589   
 590    /** Set the value of the node or attribute specified by the DOM path.
 591    * @param path DOM path
 592    * @param value node or attribute value
 593    * @param overwrite whether to overwrite (true) or add (false)
 594    * @return the node that was created, or the parent node of the attribute if it was an attribute
 595    */
 596  10 public Node set(String path, String value, boolean overwrite) {
 597  2 if (isDelegated()) { return set(path, value, _startNode, overwrite); }
 598   
 599  8 return set(path, value, _document, overwrite);
 600    }
 601   
 602   
 603    /** Set the value of the node or attribute specified by the DOM path.
 604    * @param path DOM path
 605    * @param value node or attribute value
 606    * @param n node where the search should start
 607    * @param overwrite whether to overwrite (true) or add (false) -- only applies for last node!
 608    * @return the node that was created, or the parent node of the attribute if it was an attribute
 609    */
 610  197 public Node set(String path, String value, Node n, boolean overwrite) {
 611  10 if (isDelegated()) { return _parent.set(path, value, n, overwrite); }
 612   
 613  187 int dotPos = path.lastIndexOf('.');
 614  187 Node node;
 615  187 if (dotPos == 0) {
 616  63 node = n;
 617    }
 618    else {
 619  124 node = createNode(path, n, overwrite);
 620    }
 621  187 if (dotPos >= 0) {
 622  163 Element e = (Element)node;
 623  163 e.setAttribute(path.substring(dotPos+1),value);
 624    }
 625    else {
 626  24 node.appendChild(_document.createTextNode(value));
 627    }
 628  187 return node;
 629    }
 630   
 631    /** Create the node specified by the DOM path.
 632    * @param path DOM path
 633    * @return the node that was created, or the parent node of the attribute if it was an attribute
 634    */
 635  27 public Node createNode(String path) {
 636  0 if (isDelegated()) { return createNode(path, _startNode, true); }
 637   
 638  27 return createNode(path, _document, true);
 639    }
 640   
 641    /** Create the node specified by the DOM path.
 642    * @param path DOM path
 643    * @param n node where the search should start, or null for the root
 644    * @return the node that was created, or the parent node of the attribute if it was an attribute
 645    */
 646  0 public Node createNode(String path, Node n) {
 647  0 return createNode(path, n, true);
 648    }
 649   
 650    /** Create the node specified by the DOM path.
 651    * @param path DOM path
 652    * @param n node where the search should start, or null for the root
 653    * @param overwrite whether to overwrite (true) or add (false) -- only applies for last node!
 654    * @return the node that was created, or the parent node of the attribute if it was an attribute
 655    */
 656  172 public Node createNode(String path, Node n, boolean overwrite) {
 657  0 if (isDelegated()) { return _parent.createNode(path, n, overwrite); }
 658   
 659  21 if (n == null) { n = _document; }
 660  172 while(path.indexOf('/') > -1) {
 661  157 Node child = null;
 662  157 String nodeName = path.substring(0, path.indexOf('/'));
 663  157 path = path.substring(path.indexOf('/')+1);
 664  157 child = n.getFirstChild();
 665  157 while(child != null) {
 666  198 if (child.getNodeName().equals(nodeName)) {
 667    // found
 668  146 n = child;
 669  146 break;
 670    }
 671  52 child = child.getNextSibling();
 672    }
 673  157 if (child == null) {
 674    // not found
 675  11 child = _document.createElement(nodeName);
 676  11 n.appendChild(child);
 677  11 n = child;
 678    }
 679    }
 680   
 681  172 String nodeName;
 682  172 if (path.indexOf('.') > -1) {
 683  100 nodeName = path.substring(0, path.indexOf('.'));
 684    }
 685    else {
 686  72 if (path.length() == 0) {
 687  0 throw new XMLConfigException("Cannot set node with empty name");
 688    }
 689  72 nodeName = path;
 690    }
 691  172 Node child = null;
 692  172 if (nodeName.length() > 0) {
 693  172 if (overwrite) {
 694  135 child = n.getFirstChild();
 695  135 while(child != null) {
 696  233 if (child.getNodeName().equals(nodeName)) {
 697    // found
 698  51 n = child;
 699  51 break;
 700    }
 701  182 child = child.getNextSibling();
 702    }
 703  135 if (child == null) {
 704  84 child = _document.createElement(nodeName);
 705  84 n.appendChild(child);
 706  84 n = child;
 707    }
 708    }
 709    else {
 710  37 child = _document.createElement(nodeName);
 711  37 n.appendChild(child);
 712  37 n = child;
 713    }
 714    }
 715   
 716  172 if (path.indexOf('.') > -1) {
 717  100 if (!(n instanceof Element)) {
 718  0 throw new XMLConfigException("Node " + n.getNodeName() + " should be an element so it can contain attributes");
 719    }
 720  100 return n;
 721    }
 722    else {
 723  72 if (overwrite) {
 724  39 child = n.getFirstChild();
 725    // remove all children
 726  39 while(child != null) {
 727  4 Node temp = child.getNextSibling();
 728  4 n.removeChild(child);
 729  4 child = temp;
 730    }
 731  39 return n;
 732    }
 733    else {
 734  33 return child;
 735    }
 736    }
 737    }
 738   
 739   
 740    /** Returns a string representation of the object.
 741    * @return a string representation of the object.
 742    */
 743  40 public String toString() {
 744  40 ByteArrayOutputStream os = new ByteArrayOutputStream();
 745  40 save(os);
 746  40 return os.toString();
 747    }
 748   
 749    /** Return the path of a node as it is used in XMLConfig.
 750    * @param n node
 751    * @return path
 752    */
 753  2 public static String getNodePath(Node n) {
 754  1 if (n == null) { return ""; }
 755  1 String path = "";
 756  1 while(n.getParentNode() != null) {
 757  2 path = n.getNodeName() + "/" + path;
 758  2 n = n.getParentNode();
 759    }
 760   
 761  1 return path.substring(0,path.length()-1);
 762    }
 763   
 764    /** Exception in XMLConfig methods.
 765    */
 766    public static class XMLConfigException extends RuntimeException {
 767  1 public XMLConfigException() {
 768  1 super();
 769    }
 770   
 771  60 public XMLConfigException(String message) {
 772  60 super(message);
 773    }
 774   
 775  5 public XMLConfigException(String message, Throwable cause) {
 776  5 super(message, cause);
 777    }
 778   
 779  1 public XMLConfigException(Throwable cause) {
 780  1 super(cause);
 781    }
 782    }
 783    }