Coverage report

  %line %branch
org.apache.commons.configuration.XMLConfiguration$FileConfigurationDelegate
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.OutputStream;
 22  
 import java.io.Reader;
 23  
 import java.io.Writer;
 24  
 import java.net.URL;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Collection;
 27  
 import java.util.Iterator;
 28  
 import java.util.List;
 29  
 
 30  
 import javax.xml.parsers.DocumentBuilder;
 31  
 import javax.xml.parsers.DocumentBuilderFactory;
 32  
 import javax.xml.parsers.ParserConfigurationException;
 33  
 import javax.xml.transform.OutputKeys;
 34  
 import javax.xml.transform.Result;
 35  
 import javax.xml.transform.Source;
 36  
 import javax.xml.transform.Transformer;
 37  
 import javax.xml.transform.TransformerException;
 38  
 import javax.xml.transform.TransformerFactory;
 39  
 import javax.xml.transform.dom.DOMSource;
 40  
 import javax.xml.transform.stream.StreamResult;
 41  
 
 42  
 import org.w3c.dom.Attr;
 43  
 import org.w3c.dom.CDATASection;
 44  
 import org.w3c.dom.DOMException;
 45  
 import org.w3c.dom.Document;
 46  
 import org.w3c.dom.Element;
 47  
 import org.w3c.dom.NamedNodeMap;
 48  
 import org.w3c.dom.NodeList;
 49  
 import org.w3c.dom.Text;
 50  
 import org.xml.sax.InputSource;
 51  
 import org.apache.commons.configuration.reloading.ReloadingStrategy;
 52  
 
 53  
 /**
 54  
  * A specialized hierarchical configuration class that is able to parse XML
 55  
  * documents.
 56  
  * 
 57  
  * <p>The parsed document will be stored keeping its structure. The class also
 58  
  * tries to preserve as much information from the loaded XML document as
 59  
  * possible, including comments and processing instructions. These will be
 60  
  * contained in documents created by the <code>save()</code> methods, too.
 61  
  * 
 62  
  * @since commons-configuration 1.0
 63  
  * 
 64  
  * @author J&ouml;rg Schaible
 65  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
 66  
  * @version $Revision: 156237 $, $Date: 2005-03-05 11:26:22 +0100 (Sa, 05 Mrz 2005) $
 67  
  */
 68  
 public class XMLConfiguration extends HierarchicalConfiguration implements FileConfiguration
 69  
 {
 70  
     /** Constant for the default root element name. */
 71  
     private static final String DEFAULT_ROOT_NAME = "configuration";
 72  
 
 73  
     /** Delimiter character for attributes. */
 74  
     private static char ATTR_DELIMITER = ',';
 75  
 
 76  
     private FileConfigurationDelegate delegate = new FileConfigurationDelegate();
 77  
 
 78  
     /** The document from this configuration's data source. */
 79  
     private Document document;
 80  
 
 81  
     /** Stores the name of the root element. */
 82  
     private String rootElementName;
 83  
 
 84  
     /**
 85  
      * Creates a new instance of <code>XMLConfiguration</code>.
 86  
      */
 87  
     public XMLConfiguration()
 88  
     {
 89  
         super();
 90  
     }
 91  
 
 92  
     /**
 93  
      * Creates a new instance of <code>XMLConfiguration</code>.
 94  
      * The configuration is loaded from the specified file
 95  
      * 
 96  
      * @param fileName the name of the file to load
 97  
      * @throws ConfigurationException if the file cannot be loaded
 98  
      */
 99  
     public XMLConfiguration(String fileName) throws ConfigurationException
 100  
     {
 101  
         this();
 102  
         setFileName(fileName);
 103  
         load();
 104  
     }
 105  
 
 106  
     /**
 107  
      * Creates a new instance of <code>XMLConfiguration</code>.
 108  
      * The configuration is loaded from the specified file.
 109  
      * 
 110  
      * @param file the file
 111  
      * @throws ConfigurationException if an error occurs while loading the file
 112  
      */
 113  
     public XMLConfiguration(File file) throws ConfigurationException
 114  
     {
 115  
         this();
 116  
         setFile(file);
 117  
         if (file.exists())
 118  
         {
 119  
             load();
 120  
         }
 121  
     }
 122  
 
 123  
     /**
 124  
      * Creates a new instance of <code>XMLConfiguration</code>.
 125  
      * The configuration is loaded from the specified URL.
 126  
      * 
 127  
      * @param url the URL
 128  
      * @throws ConfigurationException if loading causes an error
 129  
      */
 130  
     public XMLConfiguration(URL url) throws ConfigurationException
 131  
     {
 132  
         this();
 133  
         setURL(url);
 134  
         load();
 135  
     }
 136  
 
 137  
     /**
 138  
      * Returns the name of the root element. If this configuration was loaded
 139  
      * from a XML document, the name of this document's root element is
 140  
      * returned. Otherwise it is possible to set a name for the root element
 141  
      * that will be used when this configuration is stored.
 142  
      * 
 143  
      * @return the name of the root element
 144  
      */
 145  
     public String getRootElementName()
 146  
     {
 147  
         if (getDocument() == null)
 148  
         {
 149  
             return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
 150  
         }
 151  
         else
 152  
         {
 153  
             return getDocument().getDocumentElement().getNodeName();
 154  
         }
 155  
     }
 156  
 
 157  
     /**
 158  
      * Sets the name of the root element. This name is used when this
 159  
      * configuration object is stored in an XML file. Note that setting the name
 160  
      * of the root element works only if this configuration has been newly
 161  
      * created. If the configuration was loaded from an XML file, the name
 162  
      * cannot be changed and an <code>UnsupportedOperationException</code>
 163  
      * exception is thrown. Whether this configuration has been loaded from an
 164  
      * XML document or not can be found out using the <code>getDocument()</code>
 165  
      * method.
 166  
      * 
 167  
      * @param name the name of the root element
 168  
      */
 169  
     public void setRootElementName(String name)
 170  
     {
 171  
         if (getDocument() != null)
 172  
         {
 173  
             throw new UnsupportedOperationException("The name of the root element "
 174  
                     + "cannot be changed when loaded from an XML document!");
 175  
         }
 176  
         rootElementName = name;
 177  
     }
 178  
     
 179  
     /**
 180  
      * Returns the XML document this configuration was loaded from. The return
 181  
      * value is <b>null</b> if this configuration was not loaded from a XML
 182  
      * document.
 183  
      * 
 184  
      * @return the XML document this configuration was loaded from
 185  
      */
 186  
     public Document getDocument()
 187  
     {
 188  
         return document;
 189  
     }
 190  
 
 191  
     /**
 192  
      * @inheritDoc
 193  
      */
 194  
     protected void addPropertyDirect(String key, Object obj)
 195  
     {
 196  
         super.addPropertyDirect(key, obj);
 197  
         delegate.possiblySave();
 198  
     }
 199  
 
 200  
     /**
 201  
      * @inheritDoc
 202  
      */
 203  
     public void clearProperty(String key)
 204  
     {
 205  
         super.clearProperty(key);
 206  
         delegate.possiblySave();
 207  
     }
 208  
 
 209  
     /**
 210  
      * @inheritDoc
 211  
      */
 212  
     public void clearTree(String key)
 213  
     {
 214  
         super.clearTree(key);
 215  
         delegate.possiblySave();
 216  
     }
 217  
     
 218  
     /**
 219  
      * @inheritDoc
 220  
      */
 221  
     public void setProperty(String key, Object value)
 222  
     {
 223  
         super.setProperty(key, value);
 224  
         delegate.possiblySave();
 225  
     }
 226  
 
 227  
     /**
 228  
      * Initializes this configuration from an XML document.
 229  
      * 
 230  
      * @param document the document to be parsed
 231  
      * @param elemRefs a flag whether references to the XML elements should be set
 232  
      */
 233  
     public void initProperties(Document document, boolean elemRefs)
 234  
     {
 235  
         constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);
 236  
     }
 237  
 
 238  
     /**
 239  
      * Helper method for building the internal storage hierarchy. The XML
 240  
      * elements are transformed into node objects.
 241  
      * 
 242  
      * @param node the actual node
 243  
      * @param element the actual XML element
 244  
      * @param elemRefs a flag whether references to the XML elements should be set
 245  
      */
 246  
     private void constructHierarchy(Node node, Element element, boolean elemRefs)
 247  
     {
 248  
         processAttributes(node, element);
 249  
         StringBuffer buffer = new StringBuffer();
 250  
         NodeList list = element.getChildNodes();
 251  
         for (int i = 0; i < list.getLength(); i++)
 252  
         {
 253  
             org.w3c.dom.Node w3cNode = list.item(i);
 254  
             if (w3cNode instanceof Element)
 255  
             {
 256  
                 Element child = (Element) w3cNode;
 257  
                 Node childNode = new XMLNode(child.getTagName(), 
 258  
                         (elemRefs) ? child : null);
 259  
                 constructHierarchy(childNode, child, elemRefs);
 260  
                 node.addChild(childNode);
 261  
             }
 262  
             else if (w3cNode instanceof Text)
 263  
             {
 264  
                 Text data = (Text) w3cNode;
 265  
                 buffer.append(data.getData());
 266  
             }
 267  
         }
 268  
         String text = buffer.toString().trim();
 269  
         if (text.length() > 0)
 270  
         {
 271  
             node.setValue(text);
 272  
         }
 273  
     }
 274  
 
 275  
     /**
 276  
      * Helper method for constructing node objects for the attributes of the
 277  
      * given XML element.
 278  
      * 
 279  
      * @param node the actual node
 280  
      * @param element the actual XML element
 281  
      */
 282  
     private void processAttributes(Node node, Element element)
 283  
     {
 284  
         NamedNodeMap attributes = element.getAttributes();
 285  
         for (int i = 0; i < attributes.getLength(); ++i)
 286  
         {
 287  
             org.w3c.dom.Node w3cNode = attributes.item(i);
 288  
             if (w3cNode instanceof Attr)
 289  
             {
 290  
                 Attr attr = (Attr) w3cNode;
 291  
                 for (Iterator it = PropertyConverter.split(attr.getValue(), ATTR_DELIMITER).iterator(); it.hasNext();)
 292  
                 {
 293  
                     Node child = new XMLNode(ConfigurationKey.constructAttributeKey(attr.getName()), element);
 294  
                     child.setValue(it.next());
 295  
                     node.addChild(child);
 296  
                 }
 297  
             }
 298  
         }
 299  
     }
 300  
 
 301  
     /**
 302  
      * Creates a DOM document from the internal tree of configuration nodes.
 303  
      * 
 304  
      * @return the new document
 305  
      * @throws ConfigurationException if an error occurs
 306  
      */
 307  
     protected Document createDocument() throws ConfigurationException
 308  
     {
 309  
         try
 310  
         {
 311  
             if (document == null)
 312  
             {
 313  
                 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 314  
                 Document newDocument = builder.newDocument();
 315  
                 Element rootElem = newDocument.createElement(getRootElementName());
 316  
                 newDocument.appendChild(rootElem);
 317  
                 document = newDocument;
 318  
             }
 319  
 
 320  
             XMLBuilderVisitor builder = new XMLBuilderVisitor(document);
 321  
             builder.processDocument(getRoot());
 322  
             return document;
 323  
         } /* try */
 324  
         catch (DOMException domEx)
 325  
         {
 326  
             throw new ConfigurationException(domEx);
 327  
         }
 328  
         catch (ParserConfigurationException pex)
 329  
         {
 330  
             throw new ConfigurationException(pex);
 331  
         }
 332  
     }
 333  
 
 334  
     /**
 335  
      * Creates a new node object. This implementation returns an instance of the
 336  
      * <code>XMLNode</code> class.
 337  
      * 
 338  
      * @param name the node's name
 339  
      * @return the new node
 340  
      */
 341  
     protected Node createNode(String name)
 342  
     {
 343  
         return new XMLNode(name, null);
 344  
     }
 345  
 
 346  
     public void load() throws ConfigurationException
 347  
     {
 348  
         delegate.load();
 349  
     }
 350  
 
 351  
     public void load(String fileName) throws ConfigurationException
 352  
     {
 353  
         delegate.load(fileName);
 354  
     }
 355  
 
 356  
     public void load(File file) throws ConfigurationException
 357  
     {
 358  
         delegate.load(file);
 359  
     }
 360  
 
 361  
     public void load(URL url) throws ConfigurationException
 362  
     {
 363  
         delegate.load(url);
 364  
     }
 365  
 
 366  
     public void load(InputStream in) throws ConfigurationException
 367  
     {
 368  
         delegate.load(in);
 369  
     }
 370  
 
 371  
     public void load(InputStream in, String encoding) throws ConfigurationException
 372  
     {
 373  
         delegate.load(in, encoding);
 374  
     }
 375  
 
 376  
     /**
 377  
      * Load the properties from the given reader.
 378  
      * Note that the <code>clear()</code> method is not called, so
 379  
      * the properties contained in the loaded file will be added to the
 380  
      * actual set of properties.
 381  
      *
 382  
      * @param in An InputStream.
 383  
      *
 384  
      * @throws ConfigurationException
 385  
      */
 386  
     public void load(Reader in) throws ConfigurationException
 387  
     {
 388  
         try
 389  
         {
 390  
             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 391  
             Document newDocument = builder.parse(new InputSource(in));
 392  
             Document oldDocument = document;
 393  
             document = null;
 394  
             initProperties(newDocument, oldDocument == null);
 395  
             document = (oldDocument == null) ? newDocument : oldDocument;
 396  
         }
 397  
         catch (Exception e)
 398  
         {
 399  
             throw new ConfigurationException(e.getMessage(), e);
 400  
         }
 401  
     }
 402  
 
 403  
     public void save() throws ConfigurationException
 404  
     {
 405  
         delegate.save();
 406  
     }
 407  
 
 408  
     public void save(String fileName) throws ConfigurationException
 409  
     {
 410  
         delegate.save(fileName);
 411  
     }
 412  
 
 413  
     public void save(File file) throws ConfigurationException
 414  
     {
 415  
         delegate.save(file);
 416  
     }
 417  
 
 418  
     public void save(URL url) throws ConfigurationException
 419  
     {
 420  
         delegate.save(url);
 421  
     }
 422  
 
 423  
     public void save(OutputStream out) throws ConfigurationException
 424  
     {
 425  
         delegate.save(out);
 426  
     }
 427  
 
 428  
     public void save(OutputStream out, String encoding) throws ConfigurationException
 429  
     {
 430  
         delegate.save(out, encoding);
 431  
     }
 432  
 
 433  
     /**
 434  
      * Saves the configuration to the specified writer.
 435  
      * 
 436  
      * @param writer the writer used to save the configuration
 437  
      * @throws ConfigurationException if an error occurs
 438  
      */
 439  
     public void save(Writer writer) throws ConfigurationException
 440  
     {
 441  
         try
 442  
         {
 443  
             Transformer transformer = TransformerFactory.newInstance().newTransformer();
 444  
             Source source = new DOMSource(createDocument());
 445  
             Result result = new StreamResult(writer);
 446  
 
 447  
             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 448  
             transformer.transform(source, result);
 449  
         }
 450  
         catch (TransformerException e)
 451  
         {
 452  
             throw new ConfigurationException(e.getMessage(), e);
 453  
         }
 454  
     }
 455  
 
 456  
     public String getFileName()
 457  
     {
 458  
         return delegate.getFileName();
 459  
     }
 460  
 
 461  
     public void setFileName(String fileName)
 462  
     {
 463  
         delegate.setFileName(fileName);
 464  
     }
 465  
 
 466  
     public String getBasePath()
 467  
     {
 468  
         return delegate.getBasePath();
 469  
     }
 470  
 
 471  
     public void setBasePath(String basePath)
 472  
     {
 473  
         delegate.setBasePath(basePath);
 474  
     }
 475  
 
 476  
     public File getFile()
 477  
     {
 478  
         return delegate.getFile();
 479  
     }
 480  
 
 481  
     public void setFile(File file)
 482  
     {
 483  
         delegate.setFile(file);
 484  
     }
 485  
 
 486  
     public URL getURL()
 487  
     {
 488  
         return delegate.getURL();
 489  
     }
 490  
 
 491  
     public void setURL(URL url)
 492  
     {
 493  
         delegate.setURL(url);
 494  
     }
 495  
 
 496  
     public void setAutoSave(boolean autoSave)
 497  
     {
 498  
         delegate.setAutoSave(autoSave);
 499  
     }
 500  
 
 501  
     public boolean isAutoSave()
 502  
     {
 503  
         return delegate.isAutoSave();
 504  
     }
 505  
 
 506  
     public ReloadingStrategy getReloadingStrategy()
 507  
     {
 508  
         return delegate.getReloadingStrategy();
 509  
     }
 510  
 
 511  
     public void setReloadingStrategy(ReloadingStrategy strategy)
 512  
     {
 513  
         delegate.setReloadingStrategy(strategy);
 514  
     }
 515  
 
 516  
     public void reload()
 517  
     {
 518  
         delegate.reload();
 519  
     }
 520  
 
 521  
     public String getEncoding()
 522  
     {
 523  
         return delegate.getEncoding();
 524  
     }
 525  
 
 526  
     public void setEncoding(String encoding)
 527  
     {
 528  
         delegate.setEncoding(encoding);
 529  
     }
 530  
 
 531  
     /**
 532  
      * A specialized <code>Node</code> class that is connected with an XML
 533  
      * element. Changes on a node are also performed on the associated element.
 534  
      */
 535  
     class XMLNode extends Node
 536  
     {
 537  
         /**
 538  
          * Creates a new instance of <code>XMLNode</code> and initializes it
 539  
          * with the corresponding XML element.
 540  
          * 
 541  
          * @param elem the XML element
 542  
          */
 543  
         public XMLNode(Element elem)
 544  
         {
 545  
             super();
 546  
             setReference(elem);
 547  
         }
 548  
 
 549  
         /**
 550  
          * Creates a new instance of <code>XMLNode</code> and initializes it
 551  
          * with a name and the corresponding XML element.
 552  
          * 
 553  
          * @param name the node's name
 554  
          * @param elem the XML element
 555  
          */
 556  
         public XMLNode(String name, Element elem)
 557  
         {
 558  
             super(name);
 559  
             setReference(elem);
 560  
         }
 561  
 
 562  
         /**
 563  
          * Sets the value of this node. If this node is associated with an XML
 564  
          * element, this element will be updated, too.
 565  
          * 
 566  
          * @param value the node's new value
 567  
          */
 568  
         public void setValue(Object value)
 569  
         {
 570  
             super.setValue(value);
 571  
 
 572  
             if (getReference() != null && document != class="keyword">null)
 573  
             {
 574  
                 if (ConfigurationKey.isAttributeKey(getName()))
 575  
                 {
 576  
                     updateAttribute();
 577  
                 }
 578  
                 else
 579  
                 {
 580  
                     updateElement(value);
 581  
                 }
 582  
             }
 583  
         }
 584  
 
 585  
         /**
 586  
          * Updates the associated XML elements when a node is removed.
 587  
          */
 588  
         protected void removeReference()
 589  
         {
 590  
             if (getReference() != null)
 591  
             {
 592  
                 Element element = (Element) getReference();
 593  
                 if (ConfigurationKey.isAttributeKey(getName()))
 594  
                 {
 595  
                     updateAttribute();
 596  
                 }
 597  
                 else
 598  
                 {
 599  
                     org.w3c.dom.Node parentElem = element.getParentNode();
 600  
                     if (parentElem != null)
 601  
                     {
 602  
                         parentElem.removeChild(element);
 603  
                     }
 604  
                 }
 605  
             }
 606  
         }
 607  
 
 608  
         /**
 609  
          * Updates the node's value if it represents an element node.
 610  
          * 
 611  
          * @param value the new value
 612  
          */
 613  
         private void updateElement(Object value)
 614  
         {
 615  
             Text txtNode = findTextNodeForUpdate();
 616  
             if (value == null)
 617  
             {
 618  
                 // remove text
 619  
                 if (txtNode != null)
 620  
                 {
 621  
                     ((Element) getReference()).removeChild(txtNode);
 622  
                 }
 623  
             }
 624  
             else
 625  
             {
 626  
                 if (txtNode == null)
 627  
                 {
 628  
                     txtNode = document.createTextNode(value.toString());
 629  
                     if (((Element) getReference()).getFirstChild() != null)
 630  
                     {
 631  
                         ((Element) getReference()).insertBefore(txtNode, ((Element) getReference()).getFirstChild());
 632  
                     }
 633  
                     else
 634  
                     {
 635  
                         ((Element) getReference()).appendChild(txtNode);
 636  
                     }
 637  
                 }
 638  
                 else
 639  
                 {
 640  
                     txtNode.setNodeValue(value.toString());
 641  
                 }
 642  
             }
 643  
         }
 644  
 
 645  
         /**
 646  
          * Updates the node's value if it represents an attribute.
 647  
          *  
 648  
          */
 649  
         private void updateAttribute()
 650  
         {
 651  
             XMLBuilderVisitor.updateAttribute(getParent(), getName());
 652  
         }
 653  
 
 654  
         /**
 655  
          * Returns the only text node of this element for update. This method is
 656  
          * called when the element's text changes. Then all text nodes except
 657  
          * for the first are removed. A reference to the first is returned or
 658  
          * <b>null </b> if there is no text node at all.
 659  
          * 
 660  
          * @return the first and only text node
 661  
          */
 662  
         private Text findTextNodeForUpdate()
 663  
         {
 664  
             Text result = null;
 665  
             Element elem = (Element) getReference();
 666  
             // Find all Text nodes
 667  
             NodeList children = elem.getChildNodes();
 668  
             Collection textNodes = new ArrayList();
 669  
             for (int i = 0; i < children.getLength(); i++)
 670  
             {
 671  
                 org.w3c.dom.Node nd = children.item(i);
 672  
                 if (nd instanceof Text)
 673  
                 {
 674  
                     if (result == null)
 675  
                     {
 676  
                         result = (Text) nd;
 677  
                     }
 678  
                     else
 679  
                     {
 680  
                         textNodes.add(nd);
 681  
                     }
 682  
                 }
 683  
             }
 684  
 
 685  
             // We don't want CDATAs
 686  
             if (result instanceof CDATASection)
 687  
             {
 688  
                 textNodes.add(result);
 689  
                 result = null;
 690  
             }
 691  
 
 692  
             // Remove all but the first Text node
 693  
             for (Iterator it = textNodes.iterator(); it.hasNext();)
 694  
             {
 695  
                 elem.removeChild((org.w3c.dom.Node) it.next());
 696  
             }
 697  
             return result;
 698  
         }
 699  
     }
 700  
 
 701  
     /**
 702  
      * A concrete <code>BuilderVisitor</code> that can construct XML
 703  
      * documents.
 704  
      */
 705  
     static class XMLBuilderVisitor extends BuilderVisitor
 706  
     {
 707  
         /** Stores the document to be constructed. */
 708  
         private Document document;
 709  
 
 710  
         /**
 711  
          * Creates a new instance of <code>XMLBuilderVisitor</code>
 712  
          * 
 713  
          * @param doc the document to be created
 714  
          */
 715  
         public XMLBuilderVisitor(Document doc)
 716  
         {
 717  
             document = doc;
 718  
         }
 719  
 
 720  
         /**
 721  
          * Processes the node hierarchy and adds new nodes to the document.
 722  
          * 
 723  
          * @param rootNode the root node
 724  
          */
 725  
         public void processDocument(Node rootNode)
 726  
         {
 727  
             rootNode.visit(this, null);
 728  
         }
 729  
 
 730  
         /**
 731  
          * @inheritDoc
 732  
          */
 733  
         protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
 734  
         {
 735  
             if (ConfigurationKey.isAttributeKey(newNode.getName()))
 736  
             {
 737  
                 updateAttribute(parent, getElement(parent), newNode.getName());
 738  
                 return null;
 739  
             }
 740  
 
 741  
             else
 742  
             {
 743  
                 Element elem = document.createElement(newNode.getName());
 744  
                 if (newNode.getValue() != null)
 745  
                 {
 746  
                     elem.appendChild(document.createTextNode(newNode.getValue().toString()));
 747  
                 }
 748  
                 if (sibling2 == null)
 749  
                 {
 750  
                     getElement(parent).appendChild(elem);
 751  
                 }
 752  
                 else if (sibling1 != null)
 753  
                 {
 754  
                     getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
 755  
                 }
 756  
                 else
 757  
                 {
 758  
                     getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
 759  
                 }
 760  
                 return elem;
 761  
             }
 762  
         }
 763  
 
 764  
         /**
 765  
          * Helper method for updating the value of the specified node's
 766  
          * attribute with the given name.
 767  
          * 
 768  
          * @param node the affected node
 769  
          * @param elem the element that is associated with this node
 770  
          * @param name the name of the affected attribute
 771  
          */
 772  
         private static void updateAttribute(Node node, Element elem, String name)
 773  
         {
 774  
             if (node != null && elem != class="keyword">null)
 775  
             {
 776  
                 List attrs = node.getChildren(name);
 777  
                 StringBuffer buf = new StringBuffer();
 778  
                 for (Iterator it = attrs.iterator(); it.hasNext();)
 779  
                 {
 780  
                     Node attr = (Node) it.next();
 781  
                     if (attr.getValue() != null)
 782  
                     {
 783  
                         if (buf.length() > 0)
 784  
                         {
 785  
                             buf.append(ATTR_DELIMITER);
 786  
                         }
 787  
                         buf.append(attr.getValue());
 788  
                     }
 789  
                     attr.setReference(elem);
 790  
                 }
 791  
 
 792  
                 if (buf.length() < 1)
 793  
                 {
 794  
                     elem.removeAttribute(ConfigurationKey.removeAttributeMarkers(name));
 795  
                 }
 796  
                 else
 797  
                 {
 798  
                     elem.setAttribute(ConfigurationKey.removeAttributeMarkers(name), buf.toString());
 799  
                 }
 800  
             }
 801  
         }
 802  
 
 803  
         /**
 804  
          * Updates the value of the specified attribute of the given node.
 805  
          * Because there can be multiple child nodes representing this attribute
 806  
          * the new value is determined by iterating over all those child nodes.
 807  
          * 
 808  
          * @param node the affected node
 809  
          * @param name the name of the attribute
 810  
          */
 811  
         static void updateAttribute(Node node, String name)
 812  
         {
 813  
             if (node != null)
 814  
             {
 815  
                 updateAttribute(node, (Element) node.getReference(), name);
 816  
             }
 817  
         }
 818  1944
 
 819  
         /**
 820  
          * Helper method for accessing the element of the specified node.
 821  
          * 
 822  981
          * @param node the node
 823  981
          * @return the element of this node
 824  
          */
 825  
         private Element getElement(Node node)
 826  
         {
 827  63
             // special treatement for root node of the hierarchy
 828  63
             return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement();
 829  
         }
 830  
     }
 831  
 
 832  3108
     private class FileConfigurationDelegate extends AbstractFileConfiguration
 833  
     {
 834  
         public void load(Reader in) throws ConfigurationException
 835  
         {
 836  1582
             XMLConfiguration.this.load(in);
 837  1582
         }
 838  
 
 839  
         public void save(Writer out) throws ConfigurationException
 840  
         {
 841  112
             XMLConfiguration.this.save(out);
 842  112
         }
 843  
     }
 844  
 }

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