Coverage report

  %line %branch
org.apache.commons.configuration.XMLConfiguration$XMLBuilderVisitor
100% 
100% 

 1  
 /*
 2  
  * Copyright 2004-2005 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License")
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.apache.commons.configuration;
 18  
 
 19  
 import java.io.File;
 20  
 import java.io.InputStream;
 21  
 import java.io.Reader;
 22  
 import java.io.Writer;
 23  
 import java.net.URL;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Collection;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 
 29  
 import javax.xml.parsers.DocumentBuilder;
 30  
 import javax.xml.parsers.DocumentBuilderFactory;
 31  
 import javax.xml.parsers.ParserConfigurationException;
 32  
 import javax.xml.transform.OutputKeys;
 33  
 import javax.xml.transform.Result;
 34  
 import javax.xml.transform.Source;
 35  
 import javax.xml.transform.Transformer;
 36  
 import javax.xml.transform.TransformerException;
 37  
 import javax.xml.transform.TransformerFactory;
 38  
 import javax.xml.transform.dom.DOMSource;
 39  
 import javax.xml.transform.stream.StreamResult;
 40  
 
 41  
 import org.w3c.dom.Attr;
 42  
 import org.w3c.dom.CDATASection;
 43  
 import org.w3c.dom.DOMException;
 44  
 import org.w3c.dom.Document;
 45  
 import org.w3c.dom.Element;
 46  
 import org.w3c.dom.NamedNodeMap;
 47  
 import org.w3c.dom.NodeList;
 48  
 import org.w3c.dom.Text;
 49  
 import org.xml.sax.InputSource;
 50  
 import org.xml.sax.SAXException;
 51  
 import org.xml.sax.SAXParseException;
 52  
 import org.xml.sax.helpers.DefaultHandler;
 53  
 
 54  
 /**
 55  
  * <p>A specialized hierarchical configuration class that is able to parse XML
 56  
  * documents.</p>
 57  
  *
 58  
  * <p>The parsed document will be stored keeping its structure. The class also
 59  
  * tries to preserve as much information from the loaded XML document as
 60  
  * possible, including comments and processing instructions. These will be
 61  
  * contained in documents created by the <code>save()</code> methods, too.</p>
 62  
  *
 63  
  * <p>Like other file based configuration classes this class maintains the name
 64  
  * and path to the loaded configuration file. These properties can be altered
 65  
  * using several setter methods, but they are not modified by <code>save()</code>
 66  
  * and <code>load()</code> methods. If XML documents contain relative paths to
 67  
  * other documents (e.g. to a DTD), these references are resolved based on the
 68  
  * path set for this configuration.</p>
 69  
  *
 70  
  * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
 71  
  * provides some extended functionaly, e.g. interpolation of property values.
 72  
  * Like in <code>{@link PropertiesConfiguration}</code> property values can
 73  
  * contain delimiter characters (the comma ',' per default) and are then splitted
 74  
  * into multiple values. This works for XML attributes and text content of
 75  
  * elements as well. The delimiter can be escaped by a backslash. As an example
 76  
  * consider the following XML fragment:</p>
 77  
  *
 78  
  * <p>
 79  
  * <pre>
 80  
  * &lt;config&gt;
 81  
  *   &lt;array&gt;10,20,30,40&lt;/array&gt;
 82  
  *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
 83  
  *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
 84  
  * &lt;/config&gt;
 85  
  * </pre>
 86  
  * </p>
 87  
  * <p>Here the content of the <code>array</code> element will be splitted at
 88  
  * the commas, so the <code>array</code> key will be assigned 4 values. In the
 89  
  * <code>scalar</code> property and the <code>text</code> attribute of the
 90  
  * <code>cite</code> element the comma is escaped, so that no splitting is
 91  
  * performed.</p>
 92  
  *
 93  
  * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
 94  
  * interface and thus provides full support for loading XML documents from
 95  
  * different sources like files, URLs, or streams. A full description of these
 96  
  * features can be found in the documentation of
 97  
  * <code>{@link AbstractFileConfiguration}</code>.</p>
 98  
  *
 99  
  * @since commons-configuration 1.0
 100  
  *
 101  
  * @author J&ouml;rg Schaible
 102  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
 103  
  * @version $Revision$, $Date: 2005-11-20 20:39:51 +0100 (Sun, 20 Nov 2005) $
 104  
  */
 105  
 public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
 106  
 {
 107  
     /** Constant for the default root element name. */
 108  
     private static final String DEFAULT_ROOT_NAME = "configuration";
 109  
 
 110  
     /** The document from this configuration's data source. */
 111  
     private Document document;
 112  
 
 113  
     /** Stores the name of the root element. */
 114  
     private String rootElementName;
 115  
 
 116  
     /** Stores the document builder that should be used for loading.*/
 117  
     private DocumentBuilder documentBuilder;
 118  
 
 119  
     /** Stores a flag whether DTD validation should be performed.*/
 120  
     private boolean validating;
 121  
 
 122  
     /**
 123  
      * Creates a new instance of <code>XMLConfiguration</code>.
 124  
      */
 125  
     public XMLConfiguration()
 126  
     {
 127  
         super();
 128  
     }
 129  
 
 130  
     /**
 131  
      * Creates a new instance of <code>XMLConfiguration</code>.
 132  
      * The configuration is loaded from the specified file
 133  
      *
 134  
      * @param fileName the name of the file to load
 135  
      * @throws ConfigurationException if the file cannot be loaded
 136  
      */
 137  
     public XMLConfiguration(String fileName) throws ConfigurationException
 138  
     {
 139  
         this();
 140  
         setFileName(fileName);
 141  
         load();
 142  
     }
 143  
 
 144  
     /**
 145  
      * Creates a new instance of <code>XMLConfiguration</code>.
 146  
      * The configuration is loaded from the specified file.
 147  
      *
 148  
      * @param file the file
 149  
      * @throws ConfigurationException if an error occurs while loading the file
 150  
      */
 151  
     public XMLConfiguration(File file) throws ConfigurationException
 152  
     {
 153  
         this();
 154  
         setFile(file);
 155  
         if (file.exists())
 156  
         {
 157  
             load();
 158  
         }
 159  
     }
 160  
 
 161  
     /**
 162  
      * Creates a new instance of <code>XMLConfiguration</code>.
 163  
      * The configuration is loaded from the specified URL.
 164  
      *
 165  
      * @param url the URL
 166  
      * @throws ConfigurationException if loading causes an error
 167  
      */
 168  
     public XMLConfiguration(URL url) throws ConfigurationException
 169  
     {
 170  
         this();
 171  
         setURL(url);
 172  
         load();
 173  
     }
 174  
 
 175  
     /**
 176  
      * Returns the name of the root element. If this configuration was loaded
 177  
      * from a XML document, the name of this document's root element is
 178  
      * returned. Otherwise it is possible to set a name for the root element
 179  
      * that will be used when this configuration is stored.
 180  
      *
 181  
      * @return the name of the root element
 182  
      */
 183  
     public String getRootElementName()
 184  
     {
 185  
         if (getDocument() == null)
 186  
         {
 187  
             return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
 188  
         }
 189  
         else
 190  
         {
 191  
             return getDocument().getDocumentElement().getNodeName();
 192  
         }
 193  
     }
 194  
 
 195  
     /**
 196  
      * Sets the name of the root element. This name is used when this
 197  
      * configuration object is stored in an XML file. Note that setting the name
 198  
      * of the root element works only if this configuration has been newly
 199  
      * created. If the configuration was loaded from an XML file, the name
 200  
      * cannot be changed and an <code>UnsupportedOperationException</code>
 201  
      * exception is thrown. Whether this configuration has been loaded from an
 202  
      * XML document or not can be found out using the <code>getDocument()</code>
 203  
      * method.
 204  
      *
 205  
      * @param name the name of the root element
 206  
      */
 207  
     public void setRootElementName(String name)
 208  
     {
 209  
         if (getDocument() != null)
 210  
         {
 211  
             throw new UnsupportedOperationException("The name of the root element "
 212  
                     + "cannot be changed when loaded from an XML document!");
 213  
         }
 214  
         rootElementName = name;
 215  
     }
 216  
 
 217  
     /**
 218  
      * Returns the <code>DocumentBuilder</code> object that is used for
 219  
      * loading documents. If no specific builder has been set, this method
 220  
      * returns <b>null</b>.
 221  
      *
 222  
      * @return the <code>DocumentBuilder</code> for loading new documents
 223  
      * @since 1.2
 224  
      */
 225  
     public DocumentBuilder getDocumentBuilder()
 226  
     {
 227  
         return documentBuilder;
 228  
     }
 229  
 
 230  
     /**
 231  
      * Sets the <code>DocumentBuilder</code> object to be used for loading
 232  
      * documents. This method makes it possible to specify the exact document
 233  
      * builder. So an application can create a builder, configure it for its
 234  
      * special needs, and then pass it to this method.
 235  
      *
 236  
      * @param documentBuilder the document builder to be used; if undefined, a
 237  
      * default builder will be used
 238  
      * @since 1.2
 239  
      */
 240  
     public void setDocumentBuilder(DocumentBuilder documentBuilder)
 241  
     {
 242  
         this.documentBuilder = documentBuilder;
 243  
     }
 244  
 
 245  
     /**
 246  
      * Returns the value of the validating flag.
 247  
      *
 248  
      * @return the validating flag
 249  
      * @since 1.2
 250  
      */
 251  
     public boolean isValidating()
 252  
     {
 253  
         return validating;
 254  
     }
 255  
 
 256  
     /**
 257  
      * Sets the value of the validating flag. This flag determines whether
 258  
      * DTD validation should be performed when loading XML documents. This
 259  
      * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
 260  
      *
 261  
      * @param validating the validating flag
 262  
      * @since 1.2
 263  
      */
 264  
     public void setValidating(boolean validating)
 265  
     {
 266  
         this.validating = validating;
 267  
     }
 268  
 
 269  
     /**
 270  
      * Returns the XML document this configuration was loaded from. The return
 271  
      * value is <b>null</b> if this configuration was not loaded from a XML
 272  
      * document.
 273  
      *
 274  
      * @return the XML document this configuration was loaded from
 275  
      */
 276  
     public Document getDocument()
 277  
     {
 278  
         return document;
 279  
     }
 280  
 
 281  
     /**
 282  
      * Removes all properties from this configuration. If this configuration
 283  
      * was loaded from a file, the associated DOM document is also cleared.
 284  
      */
 285  
     public void clear()
 286  
     {
 287  
         super.clear();
 288  
         document = null;
 289  
     }
 290  
 
 291  
     /**
 292  
      * Initializes this configuration from an XML document.
 293  
      *
 294  
      * @param document the document to be parsed
 295  
      * @param elemRefs a flag whether references to the XML elements should be set
 296  
      */
 297  
     public void initProperties(Document document, boolean elemRefs)
 298  
     {
 299  
         constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);
 300  
     }
 301  
 
 302  
     /**
 303  
      * Helper method for building the internal storage hierarchy. The XML
 304  
      * elements are transformed into node objects.
 305  
      *
 306  
      * @param node the actual node
 307  
      * @param element the actual XML element
 308  
      * @param elemRefs a flag whether references to the XML elements should be set
 309  
      */
 310  
     private void constructHierarchy(Node node, Element element, boolean elemRefs)
 311  
     {
 312  
         processAttributes(node, element, elemRefs);
 313  
         StringBuffer buffer = new StringBuffer();
 314  
         NodeList list = element.getChildNodes();
 315  
         for (int i = 0; i < list.getLength(); i++)
 316  
         {
 317  
             org.w3c.dom.Node w3cNode = list.item(i);
 318  
             if (w3cNode instanceof Element)
 319  
             {
 320  
                 Element child = (Element) w3cNode;
 321  
                 Node childNode = new XMLNode(child.getTagName(),
 322  
                         elemRefs ? child : null);
 323  
                 constructHierarchy(childNode, child, elemRefs);
 324  
                 node.addChild(childNode);
 325  
                 handleDelimiters(node, childNode);
 326  
             }
 327  
             else if (w3cNode instanceof Text)
 328  
             {
 329  
                 Text data = (Text) w3cNode;
 330  
                 buffer.append(data.getData());
 331  
             }
 332  
         }
 333  
         String text = buffer.toString().trim();
 334  
         if (text.length() > 0 || !node.hasChildren())
 335  
         {
 336  
             node.setValue(text);
 337  
         }
 338  
     }
 339  
 
 340  
     /**
 341  
      * Helper method for constructing node objects for the attributes of the
 342  
      * given XML element.
 343  
      *
 344  
      * @param node the actual node
 345  
      * @param element the actual XML element
 346  
      * @param elemRefs a flag whether references to the XML elements should be set
 347  
      */
 348  
     private void processAttributes(Node node, Element element, boolean elemRefs)
 349  
     {
 350  
         NamedNodeMap attributes = element.getAttributes();
 351  
         for (int i = 0; i < attributes.getLength(); ++i)
 352  
         {
 353  
             org.w3c.dom.Node w3cNode = attributes.item(i);
 354  
             if (w3cNode instanceof Attr)
 355  
             {
 356  
                 Attr attr = (Attr) w3cNode;
 357  
                 for (Iterator it = PropertyConverter.split(attr.getValue(), getDelimiter()).iterator(); it.hasNext();)
 358  
                 {
 359  
                     Node child = new XMLNode(ConfigurationKey.constructAttributeKey(attr.getName()),
 360  
                             elemRefs ? element : null);
 361  
                     child.setValue(it.next());
 362  
                     node.addChild(child);
 363  
                 }
 364  
             }
 365  
         }
 366  
     }
 367  
 
 368  
     /**
 369  
      * Deals with elements whose value is a list. In this case multiple child
 370  
      * elements must be added.
 371  
      *
 372  
      * @param parent the parent element
 373  
      * @param child the child element
 374  
      */
 375  
     private void handleDelimiters(Node parent, Node child)
 376  
     {
 377  
         if (child.getValue() != null)
 378  
         {
 379  
             List values = PropertyConverter.split(child.getValue().toString(),
 380  
                     getDelimiter());
 381  
             if (values.size() > 1)
 382  
             {
 383  
                 // remove the original child
 384  
                 parent.remove(child);
 385  
                 // add multiple new children
 386  
                 for (Iterator it = values.iterator(); it.hasNext();)
 387  
                 {
 388  
                     Node c = new XMLNode(child.getName(), null);
 389  
                     c.setValue(it.next());
 390  
                     parent.addChild(c);
 391  
                 }
 392  
             }
 393  
             else if (values.size() == 1)
 394  
             {
 395  
                 // we will have to replace the value because it might
 396  
                 // contain escaped delimiters
 397  
                 child.setValue(values.get(0));
 398  
             }
 399  
         }
 400  
     }
 401  
 
 402  
     /**
 403  
      * Creates the <code>DocumentBuilder</code> to be used for loading files.
 404  
      * This implementation checks whether a specific
 405  
      * <code>DocumentBuilder</code> has been set. If this is the case, this
 406  
      * one is used. Otherwise a default builder is created. Depending on the
 407  
      * value of the validating flag this builder will be a validating or a non
 408  
      * validating <code>DocumentBuilder</code>.
 409  
      *
 410  
      * @return the <code>DocumentBuilder</code> for loading configuration
 411  
      * files
 412  
      * @throws ParserConfigurationException if an error occurs
 413  
      * @since 1.2
 414  
      */
 415  
     protected DocumentBuilder createDocumentBuilder()
 416  
             throws ParserConfigurationException
 417  
     {
 418  
         if (getDocumentBuilder() != null)
 419  
         {
 420  
             return getDocumentBuilder();
 421  
         }
 422  
         else
 423  
         {
 424  
             DocumentBuilderFactory factory = DocumentBuilderFactory
 425  
                     .newInstance();
 426  
             factory.setValidating(isValidating());
 427  
             DocumentBuilder result = factory.newDocumentBuilder();
 428  
 
 429  
             if (isValidating())
 430  
             {
 431  
                 // register an error handler which detects validation errors
 432  
                 result.setErrorHandler(new DefaultHandler()
 433  
                 {
 434  
                     public void error(SAXParseException ex) throws SAXException
 435  
                     {
 436  
                         throw ex;
 437  
                     }
 438  
                 });
 439  
             }
 440  
             return result;
 441  
         }
 442  
     }
 443  
 
 444  
     /**
 445  
      * Creates a DOM document from the internal tree of configuration nodes.
 446  
      *
 447  
      * @return the new document
 448  
      * @throws ConfigurationException if an error occurs
 449  
      */
 450  
     protected Document createDocument() throws ConfigurationException
 451  
     {
 452  
         try
 453  
         {
 454  
             if (document == null)
 455  
             {
 456  
                 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 457  
                 Document newDocument = builder.newDocument();
 458  
                 Element rootElem = newDocument.createElement(getRootElementName());
 459  
                 newDocument.appendChild(rootElem);
 460  
                 document = newDocument;
 461  
             }
 462  
 
 463  
             XMLBuilderVisitor builder = new XMLBuilderVisitor(document);
 464  
             builder.processDocument(getRoot());
 465  
             return document;
 466  
         } /* try */
 467  
         catch (DOMException domEx)
 468  
         {
 469  
             throw new ConfigurationException(domEx);
 470  
         }
 471  
         catch (ParserConfigurationException pex)
 472  
         {
 473  
             throw new ConfigurationException(pex);
 474  
         }
 475  
     }
 476  
 
 477  
     /**
 478  
      * Creates a new node object. This implementation returns an instance of the
 479  
      * <code>XMLNode</code> class.
 480  
      *
 481  
      * @param name the node's name
 482  
      * @return the new node
 483  
      */
 484  
     protected Node createNode(String name)
 485  
     {
 486  
         return new XMLNode(name, null);
 487  
     }
 488  
 
 489  
     /**
 490  
      * Loads the configuration from the given input stream.
 491  
      *
 492  
      * @param in the input stream
 493  
      * @throws ConfigurationException if an error occurs
 494  
      */
 495  
     public void load(InputStream in) throws ConfigurationException
 496  
     {
 497  
         load(new InputSource(in));
 498  
     }
 499  
 
 500  
     /**
 501  
      * Load the configuration from the given reader.
 502  
      * Note that the <code>clear()</code> method is not called, so
 503  
      * the properties contained in the loaded file will be added to the
 504  
      * actual set of properties.
 505  
      *
 506  
      * @param in An InputStream.
 507  
      *
 508  
      * @throws ConfigurationException if an error occurs
 509  
      */
 510  
     public void load(Reader in) throws ConfigurationException
 511  
     {
 512  
         load(new InputSource(in));
 513  
     }
 514  
 
 515  
     /**
 516  
      * Loads a configuration file from the specified input source.
 517  
      * @param source the input source
 518  
      * @throws ConfigurationException if an error occurs
 519  
      */
 520  
     private void load(InputSource source) throws ConfigurationException
 521  
     {
 522  
         try
 523  
         {
 524  
             URL sourceURL = getDelegate().getURL();
 525  
             if (sourceURL != null)
 526  
             {
 527  
                 source.setSystemId(sourceURL.toString());
 528  
             }
 529  
 
 530  
             DocumentBuilder builder = createDocumentBuilder();
 531  
             Document newDocument = builder.parse(source);
 532  
             Document oldDocument = document;
 533  
             document = null;
 534  
             initProperties(newDocument, oldDocument == null);
 535  
             document = (oldDocument == null) ? newDocument : oldDocument;
 536  
         }
 537  
         catch (Exception e)
 538  
         {
 539  
             throw new ConfigurationException(e.getMessage(), e);
 540  
         }
 541  
     }
 542  
 
 543  
     /**
 544  
      * Saves the configuration to the specified writer.
 545  
      *
 546  
      * @param writer the writer used to save the configuration
 547  
      * @throws ConfigurationException if an error occurs
 548  
      */
 549  
     public void save(Writer writer) throws ConfigurationException
 550  
     {
 551  
         try
 552  
         {
 553  
             Transformer transformer = TransformerFactory.newInstance().newTransformer();
 554  
             Source source = new DOMSource(createDocument());
 555  
             Result result = new StreamResult(writer);
 556  
 
 557  
             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 558  
             if (getEncoding() != null)
 559  
             {
 560  
                 transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
 561  
             }
 562  
             transformer.transform(source, result);
 563  
         }
 564  
         catch (TransformerException e)
 565  
         {
 566  
             throw new ConfigurationException(e.getMessage(), e);
 567  
         }
 568  
     }
 569  
 
 570  
     /**
 571  
      * Creates a copy of this object. The new configuration object will contain
 572  
      * the same properties as the original, but it will lose any connection to a
 573  
      * source document (if one exists). This is to avoid race conditions if both
 574  
      * the original and the copy are modified and then saved.
 575  
      *
 576  
      * @return the copy
 577  
      */
 578  
     public Object clone()
 579  
     {
 580  
         XMLConfiguration copy = (XMLConfiguration) super.clone();
 581  
 
 582  
         // clear document related properties
 583  
         copy.document = null;
 584  
         copy.setDelegate(createDelegate());
 585  
         // clear all references in the nodes, too
 586  
         copy.getRoot().visit(new NodeVisitor()
 587  
         {
 588  
             public void visitBeforeChildren(Node node, ConfigurationKey key)
 589  
             {
 590  
                 node.setReference(null);
 591  
             }
 592  
         }, null);
 593  
 
 594  
         return copy;
 595  
     }
 596  
 
 597  
     /**
 598  
      * Creates the file configuration delegate for this object. This implementation
 599  
      * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
 600  
      * that deals with some specialities of <code>XMLConfiguration</code>.
 601  
      * @return the delegate for this object
 602  
      */
 603  
     protected FileConfigurationDelegate createDelegate()
 604  
     {
 605  
         return new XMLFileConfigurationDelegate();
 606  
     }
 607  
 
 608  
     /**
 609  
      * A specialized <code>Node</code> class that is connected with an XML
 610  
      * element. Changes on a node are also performed on the associated element.
 611  
      */
 612  
     class XMLNode extends Node
 613  
     {
 614  
         /**
 615  
          * Creates a new instance of <code>XMLNode</code> and initializes it
 616  
          * with a name and the corresponding XML element.
 617  
          *
 618  
          * @param name the node's name
 619  
          * @param elem the XML element
 620  
          */
 621  
         public XMLNode(String name, Element elem)
 622  
         {
 623  
             super(name);
 624  
             setReference(elem);
 625  
         }
 626  
 
 627  
         /**
 628  
          * Sets the value of this node. If this node is associated with an XML
 629  
          * element, this element will be updated, too.
 630  
          *
 631  
          * @param value the node's new value
 632  
          */
 633  
         public void setValue(Object value)
 634  
         {
 635  
             super.setValue(value);
 636  
 
 637  
             if (getReference() != null && document != class="keyword">null)
 638  
             {
 639  
                 if (ConfigurationKey.isAttributeKey(getName()))
 640  
                 {
 641  
                     updateAttribute();
 642  
                 }
 643  
                 else
 644  
                 {
 645  
                     updateElement(value);
 646  
                 }
 647  
             }
 648  
         }
 649  
 
 650  
         /**
 651  
          * Updates the associated XML elements when a node is removed.
 652  
          */
 653  
         protected void removeReference()
 654  
         {
 655  
             if (getReference() != null)
 656  
             {
 657  
                 Element element = (Element) getReference();
 658  
                 if (ConfigurationKey.isAttributeKey(getName()))
 659  
                 {
 660  
                     updateAttribute();
 661  
                 }
 662  
                 else
 663  
                 {
 664  
                     org.w3c.dom.Node parentElem = element.getParentNode();
 665  
                     if (parentElem != null)
 666  
                     {
 667  
                         parentElem.removeChild(element);
 668  
                     }
 669  
                 }
 670  
             }
 671  
         }
 672  
 
 673  
         /**
 674  
          * Updates the node's value if it represents an element node.
 675  
          *
 676  
          * @param value the new value
 677  
          */
 678  
         private void updateElement(Object value)
 679  
         {
 680  
             Text txtNode = findTextNodeForUpdate();
 681  
             if (value == null)
 682  
             {
 683  
                 // remove text
 684  
                 if (txtNode != null)
 685  
                 {
 686  
                     ((Element) getReference()).removeChild(txtNode);
 687  
                 }
 688  
             }
 689  
             else
 690  
             {
 691  
                 if (txtNode == null)
 692  
                 {
 693  
                     txtNode = document
 694  
                             .createTextNode(PropertyConverter.escapeDelimiters(
 695  
                                     value.toString(), getDelimiter()));
 696  
                     if (((Element) getReference()).getFirstChild() != null)
 697  
                     {
 698  
                         ((Element) getReference()).insertBefore(txtNode,
 699  
                                 ((Element) getReference()).getFirstChild());
 700  
                     }
 701  
                     else
 702  
                     {
 703  
                         ((Element) getReference()).appendChild(txtNode);
 704  
                     }
 705  
                 }
 706  
                 else
 707  
                 {
 708  
                     txtNode.setNodeValue(PropertyConverter.escapeDelimiters(
 709  
                             value.toString(), getDelimiter()));
 710  
                 }
 711  
             }
 712  
         }
 713  
 
 714  
         /**
 715  
          * Updates the node's value if it represents an attribute.
 716  
          *
 717  
          */
 718  
         private void updateAttribute()
 719  
         {
 720  
             XMLBuilderVisitor.updateAttribute(getParent(), getName());
 721  
         }
 722  
 
 723  
         /**
 724  
          * Returns the only text node of this element for update. This method is
 725  
          * called when the element's text changes. Then all text nodes except
 726  
          * for the first are removed. A reference to the first is returned or
 727  
          * <b>null </b> if there is no text node at all.
 728  
          *
 729  
          * @return the first and only text node
 730  
          */
 731  
         private Text findTextNodeForUpdate()
 732  
         {
 733  
             Text result = null;
 734  
             Element elem = (Element) getReference();
 735  
             // Find all Text nodes
 736  
             NodeList children = elem.getChildNodes();
 737  
             Collection textNodes = new ArrayList();
 738  
             for (int i = 0; i < children.getLength(); i++)
 739  
             {
 740  
                 org.w3c.dom.Node nd = children.item(i);
 741  
                 if (nd instanceof Text)
 742  
                 {
 743  
                     if (result == null)
 744  
                     {
 745  
                         result = (Text) nd;
 746  
                     }
 747  
                     else
 748  
                     {
 749  
                         textNodes.add(nd);
 750  
                     }
 751  
                 }
 752  
             }
 753  
 
 754  
             // We don't want CDATAs
 755  
             if (result instanceof CDATASection)
 756  
             {
 757  
                 textNodes.add(result);
 758  
                 result = null;
 759  
             }
 760  
 
 761  
             // Remove all but the first Text node
 762  
             for (Iterator it = textNodes.iterator(); it.hasNext();)
 763  
             {
 764  
                 elem.removeChild((org.w3c.dom.Node) it.next());
 765  
             }
 766  
             return result;
 767  
         }
 768  
     }
 769  
 
 770  
     /**
 771  
      * A concrete <code>BuilderVisitor</code> that can construct XML
 772  
      * documents.
 773  
      */
 774  
     static class XMLBuilderVisitor extends BuilderVisitor
 775  
     {
 776  
         /** Stores the document to be constructed. */
 777  
         private Document document;
 778  
 
 779  
         /**
 780  
          * Creates a new instance of <code>XMLBuilderVisitor</code>
 781  
          *
 782  
          * @param doc the document to be created
 783  
          */
 784  
         public XMLBuilderVisitor(Document doc)
 785  51
         {
 786  51
             document = doc;
 787  51
         }
 788  
 
 789  
         /**
 790  
          * Processes the node hierarchy and adds new nodes to the document.
 791  
          *
 792  
          * @param rootNode the root node
 793  
          */
 794  
         public void processDocument(Node rootNode)
 795  
         {
 796  51
             rootNode.visit(this, null);
 797  51
         }
 798  
 
 799  
         /**
 800  
          * Inserts a new node. This implementation ensures that the correct
 801  
          * XML element is created and inserted between the given siblings.
 802  
          *
 803  
          * @param newNode the node to insert
 804  
          * @param parent the parent node
 805  
          * @param sibling1 the first sibling
 806  
          * @param sibling2 the second sibling
 807  
          * @return the new node
 808  
          */
 809  
         protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
 810  
         {
 811  273
             if (ConfigurationKey.isAttributeKey(newNode.getName()))
 812  
             {
 813  15
                 updateAttribute(parent, getElement(parent), newNode.getName());
 814  15
                 return null;
 815  
             }
 816  
 
 817  
             else
 818  
             {
 819  258
                 Element elem = document.createElement(newNode.getName());
 820  258
                 if (newNode.getValue() != null)
 821  
                 {
 822  198
                     elem.appendChild(document.createTextNode(
 823  
                             PropertyConverter.escapeDelimiters(newNode.getValue().toString(), getDelimiter())));
 824  
                 }
 825  258
                 if (sibling2 == null)
 826  
                 {
 827  177
                     getElement(parent).appendChild(elem);
 828  
                 }
 829  81
                 else if (sibling1 != null)
 830  
                 {
 831  54
                     getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
 832  
                 }
 833  
                 else
 834  
                 {
 835  27
                     getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
 836  
                 }
 837  258
                 return elem;
 838  
             }
 839  
         }
 840  
 
 841  
         /**
 842  
          * Helper method for updating the value of the specified node's
 843  
          * attribute with the given name.
 844  
          *
 845  
          * @param node the affected node
 846  
          * @param elem the element that is associated with this node
 847  
          * @param name the name of the affected attribute
 848  
          */
 849  
         private static void updateAttribute(Node node, Element elem, String name)
 850  
         {
 851  294
             if (node != null && elem != class="keyword">null)
 852  
             {
 853  294
                 List attrs = node.getChildren(name);
 854  294
                 StringBuffer buf = new StringBuffer();
 855  813
                 for (Iterator it = attrs.iterator(); it.hasNext();)
 856  
                 {
 857  225
                     Node attr = (Node) it.next();
 858  225
                     if (attr.getValue() != null)
 859  
                     {
 860  90
                         if (buf.length() > 0)
 861  
                         {
 862  30
                             buf.append(getDelimiter());
 863  
                         }
 864  90
                         buf.append(PropertyConverter.escapeDelimiters(attr
 865  
                                 .getValue().toString(), getDelimiter()));
 866  
                     }
 867  225
                     attr.setReference(elem);
 868  
                 }
 869  
 
 870  294
                 if (buf.length() < 1)
 871  
                 {
 872  234
                     elem.removeAttribute(ConfigurationKey
 873  
                             .removeAttributeMarkers(name));
 874  
                 }
 875  
                 else
 876  
                 {
 877  60
                     elem.setAttribute(ConfigurationKey
 878  
                             .removeAttributeMarkers(name), buf.toString());
 879  
                 }
 880  
             }
 881  294
         }
 882  
 
 883  
         /**
 884  
          * Updates the value of the specified attribute of the given node.
 885  
          * Because there can be multiple child nodes representing this attribute
 886  
          * the new value is determined by iterating over all those child nodes.
 887  
          *
 888  
          * @param node the affected node
 889  
          * @param name the name of the attribute
 890  
          */
 891  
         static void updateAttribute(Node node, String name)
 892  
         {
 893  279
             if (node != null)
 894  
             {
 895  279
                 updateAttribute(node, (Element) node.getReference(), name);
 896  
             }
 897  279
         }
 898  
 
 899  
         /**
 900  
          * Helper method for accessing the element of the specified node.
 901  
          *
 902  
          * @param node the node
 903  
          * @return the element of this node
 904  
          */
 905  
         private Element getElement(Node node)
 906  
         {
 907  
             // special treatement for root node of the hierarchy
 908  354
             return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement();
 909  
         }
 910  
     }
 911  
 
 912  
     /**
 913  
      * A special implementation of the <code>FileConfiguration</code> interface that is
 914  
      * used internally to implement the <code>FileConfiguration</code> methods
 915  
      * for <code>XMLConfiguration</code>, too.
 916  
      */
 917  
     private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
 918  
     {
 919  
         public void load(InputStream in) throws ConfigurationException
 920  
         {
 921  
             XMLConfiguration.this.load(in);
 922  
         }
 923  
     }
 924  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.