Coverage report

  %line %branch
org.apache.commons.configuration.XMLConfiguration
87% 
100% 

 1  
 /*
 2  
  * Copyright 2004 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.Reader;
 21  
 import java.io.StringWriter;
 22  
 import java.io.Writer;
 23  
 import java.net.URL;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import javax.xml.parsers.DocumentBuilder;
 28  
 import javax.xml.parsers.DocumentBuilderFactory;
 29  
 import javax.xml.parsers.ParserConfigurationException;
 30  
 import javax.xml.transform.Result;
 31  
 import javax.xml.transform.Source;
 32  
 import javax.xml.transform.Transformer;
 33  
 import javax.xml.transform.TransformerException;
 34  
 import javax.xml.transform.TransformerFactory;
 35  
 import javax.xml.transform.dom.DOMSource;
 36  
 import javax.xml.transform.stream.StreamResult;
 37  
 
 38  
 import org.apache.commons.lang.StringUtils;
 39  
 import org.w3c.dom.Attr;
 40  
 import org.w3c.dom.CDATASection;
 41  
 import org.w3c.dom.CharacterData;
 42  
 import org.w3c.dom.Document;
 43  
 import org.w3c.dom.Element;
 44  
 import org.w3c.dom.NamedNodeMap;
 45  
 import org.w3c.dom.Node;
 46  
 import org.w3c.dom.NodeList;
 47  
 import org.w3c.dom.Text;
 48  
 import org.xml.sax.InputSource;
 49  
 
 50  
 /**
 51  
  * Reads a XML configuration file.
 52  
  *
 53  
  * To retrieve the value of an attribute of an element, use
 54  
  * <code>X.Y.Z[@attribute]</code>. The '@' symbol was chosen for consistency
 55  
  * with XPath.
 56  
  *
 57  
  * Setting property values will <b>NOT </b> automatically persist changes to
 58  
  * disk, unless <code>autoSave=true</code>.
 59  
  *
 60  
  * @since commons-configuration 1.0
 61  
  *
 62  
  * @author J�rg Schaible
 63  
  * @author <a href="mailto:kelvint@apache.org">Kelvin Tan </a>
 64  
  * @author <a href="mailto:dlr@apache.org">Daniel Rall </a>
 65  
  * @author Emmanuel Bourg
 66  
  * @version $Revision: 1.17 $, $Date: 2004/10/04 19:35:45 $
 67  
  */
 68  
 public class XMLConfiguration extends AbstractFileConfiguration
 69  
 {
 70  
     // For conformance with xpath
 71  
     private static final String ATTRIBUTE_START = "[@";
 72  
 
 73  
     private static final String ATTRIBUTE_END = "]";
 74  
 
 75  
     /**
 76  
      * For consistency with properties files. Access nodes via an "A.B.C"
 77  
      * notation.
 78  
      */
 79  
     private static final String NODE_DELIMITER = ".";
 80  
 
 81  
     /**
 82  
      * The XML document from our data source.
 83  
      */
 84  
     private Document document;
 85  
 
 86  
     /**
 87  
      * If true, modifications are immediately persisted.
 88  
      */
 89  85
     private boolean autoSave = false;
 90  
 
 91  
     /**
 92  
      * Creates an empty XML configuration.
 93  
      */
 94  
     public XMLConfiguration()
 95  12
     {
 96  
         // build an empty document.
 97  12
         DocumentBuilder builder = null;
 98  
         try
 99  
         {
 100  12
             builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 101  
         }
 102  0
         catch (ParserConfigurationException e)
 103  
         {
 104  0
             throw new ConfigurationRuntimeException(e.getMessage(), e);
 105  12
         }
 106  
 
 107  12
         document = builder.newDocument();
 108  12
         document.appendChild(document.createElement("configuration"));
 109  12
     }
 110  
 
 111  
     /**
 112  
      * Creates and loads the XML configuration from the specified resource.
 113  
      *
 114  
      * @param resource The name of the resource to load.
 115  
      *
 116  
      * @throws ConfigurationException Error while loading the XML file
 117  
      */
 118  
     public XMLConfiguration(String resource) throws ConfigurationException
 119  0
     {
 120  0
         this.fileName = resource;
 121  0
         url = ConfigurationUtils.locate(resource);
 122  0
         load();
 123  0
     }
 124  
 
 125  
     /**
 126  
      * Creates and loads the XML configuration from the specified file.
 127  
      *
 128  
      * @param file The XML file to load.
 129  
      * @throws ConfigurationException Error while loading the XML file
 130  
      */
 131  
     public XMLConfiguration(File file) throws ConfigurationException
 132  73
     {
 133  73
         setFile(file);
 134  73
         load();
 135  73
     }
 136  
 
 137  
     /**
 138  
      * Creates and loads the XML configuration from the specified URL.
 139  
      *
 140  
      * @param url The location of the XML file to load.
 141  
      * @throws ConfigurationException Error while loading the XML file
 142  
      */
 143  
     public XMLConfiguration(URL url) throws ConfigurationException
 144  0
     {
 145  0
         setURL(url);
 146  0
         load();
 147  0
     }
 148  
 
 149  
     /**
 150  
      * {@inheritDoc}
 151  
      */
 152  
     public void load(Reader in) throws ConfigurationException
 153  
     {
 154  
         try
 155  
         {
 156  91
             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 157  91
             document = builder.parse(new InputSource(in));
 158  
         }
 159  0
         catch (Exception e)
 160  
         {
 161  0
             throw new ConfigurationException(e.getMessage(), e);
 162  91
         }
 163  
 
 164  91
         initProperties(document.getDocumentElement(), new StringBuffer());
 165  91
     }
 166  
 
 167  
     /**
 168  
      * Loads and initializes from the XML file.
 169  
      *
 170  
      * @param element The element to start processing from. Callers should supply the root element of the document.
 171  
      * @param hierarchy
 172  
      */
 173  
     private void initProperties(Element element, StringBuffer hierarchy)
 174  
     {
 175  3288
         StringBuffer buffer = new StringBuffer();
 176  3288
         NodeList list = element.getChildNodes();
 177  13514
         for (int i = 0; i < list.getLength(); i++)
 178  
         {
 179  10226
             Node node = list.item(i);
 180  10226
             if (node instanceof Element)
 181  
             {
 182  3197
                 Element child = (Element) node;
 183  
 
 184  3197
                 StringBuffer subhierarchy = new StringBuffer(hierarchy.toString());
 185  3197
                 subhierarchy.append(child.getTagName());
 186  3197
                 processAttributes(subhierarchy.toString(), child);
 187  3197
                 initProperties(child, subhierarchy.append(NODE_DELIMITER));
 188  
             }
 189  7029
             else if (node instanceof CDATASection || node instanceof Text)
 190  
             {
 191  6579
                 CharacterData data = (CharacterData) node;
 192  6579
                 buffer.append(data.getData());
 193  
             }
 194  
         }
 195  
 
 196  3288
         String text = buffer.toString().trim();
 197  3288
         if (text.length() > 0 && hierarchy.length() > 0)
 198  
         {
 199  2098
             super.addProperty(hierarchy.substring(0, hierarchy.length() - 1), text);
 200  
         }
 201  3288
     }
 202  
 
 203  
     /**
 204  
      * Helper method for constructing properties for the attributes of the given
 205  
      * XML element.
 206  
      *
 207  
      * @param hierarchy the actual hierarchy
 208  
      * @param element   the actual XML element
 209  
      */
 210  
     private void processAttributes(String hierarchy, Element element)
 211  
     {
 212  
         // Add attributes as x.y{ATTRIBUTE_START}att{ATTRIBUTE_END}
 213  3197
         NamedNodeMap attributes = element.getAttributes();
 214  4013
         for (int i = 0; i < attributes.getLength(); ++i)
 215  
         {
 216  816
             Attr attr = (Attr) attributes.item(i);
 217  816
             String attrName = hierarchy + ATTRIBUTE_START + attr.getName() + ATTRIBUTE_END;
 218  816
             super.addProperty(attrName, attr.getValue());
 219  
         }
 220  3197
     }
 221  
 
 222  
     /**
 223  
      * Calls super method, and also ensures the underlying {@linkDocument} is
 224  
      * modified so changes are persisted when saved.
 225  
      *
 226  
      * @param name
 227  
      * @param value
 228  
      */
 229  
     public void addProperty(String name, Object value)
 230  
     {
 231  23
         super.addProperty(name, value);
 232  23
         addXmlProperty(name, value);
 233  23
         possiblySave();
 234  23
     }
 235  
 
 236  
     Object getXmlProperty(String name)
 237  
     {
 238  
         // parse the key
 239  27
         String[] nodes = parseElementNames(name);
 240  27
         String attName = parseAttributeName(name);
 241  
 
 242  
         // get all the matching elements
 243  27
         List children = findElementsForPropertyNodes(nodes);
 244  
 
 245  27
         List properties = new ArrayList();
 246  27
         if (attName == null)
 247  
         {
 248  
             // return text contents of elements
 249  18
             Iterator cIter = children.iterator();
 250  46
             while (cIter.hasNext())
 251  
             {
 252  28
                 Element child = (Element) cIter.next();
 253  
                 // add non-empty strings
 254  28
                 String text = getChildText(child);
 255  28
                 if (StringUtils.isNotEmpty(text))
 256  
                 {
 257  19
                     properties.add(text);
 258  
                 }
 259  
             }
 260  
         }
 261  
         else
 262  
         {
 263  
             // return text contents of attributes
 264  9
             Iterator cIter = children.iterator();
 265  31
             while (cIter.hasNext())
 266  
             {
 267  22
                 Element child = (Element) cIter.next();
 268  22
                 if (child.hasAttribute(attName))
 269  
                 {
 270  9
                     properties.add(child.getAttribute(attName));
 271  
                 }
 272  
             }
 273  
         }
 274  
 
 275  27
         switch (properties.size())
 276  
         {
 277  
             case 0:
 278  14
                 return null;
 279  
             case 1:
 280  8
                 return properties.get(0);
 281  
             default:
 282  5
                 return properties;
 283  
         }
 284  
     }
 285  
 
 286  
     /**
 287  
      * TODO Add comment.
 288  
      *
 289  
      * @param nodes
 290  
      * @return
 291  
      */
 292  
     private List findElementsForPropertyNodes(String[] nodes)
 293  
     {
 294  48
         List children = new ArrayList();
 295  48
         List elements = new ArrayList();
 296  
 
 297  48
         children.add(document.getDocumentElement());
 298  140
         for (int i = 0; i < nodes.length; i++)
 299  
         {
 300  92
             elements.clear();
 301  92
             elements.addAll(children);
 302  92
             children.clear();
 303  
 
 304  92
             String eName = nodes[i];
 305  92
             Iterator eIter = elements.iterator();
 306  197
             while (eIter.hasNext())
 307  
             {
 308  105
                 Element element = (Element) eIter.next();
 309  105
                 NodeList list = element.getChildNodes();
 310  1695
                 for (int j = 0; j < list.getLength(); j++)
 311  
                 {
 312  1590
                     Node node = list.item(j);
 313  1590
                     if (node instanceof Element)
 314  
                     {
 315  594
                         Element child = (Element) node;
 316  594
                         if (eName.equals(child.getTagName()))
 317  
                         {
 318  138
                             children.add(child);
 319  
                         }
 320  
                     }
 321  
                 }
 322  
             }
 323  
         }
 324  
 
 325  48
         return children;
 326  
     }
 327  
 
 328  
     private static String getChildText(Node node)
 329  
     {
 330  
         // is there anything to do?
 331  28
         if (node == null)
 332  
         {
 333  0
             return null;
 334  
         }
 335  
 
 336  
         // concatenate children text
 337  28
         StringBuffer str = new StringBuffer();
 338  28
         Node child = node.getFirstChild();
 339  61
         while (child != null)
 340  
         {
 341  33
             short type = child.getNodeType();
 342  33
             if (type == Node.TEXT_NODE)
 343  
             {
 344  25
                 str.append(child.getNodeValue());
 345  
             }
 346  8
             else if (type == Node.CDATA_SECTION_NODE)
 347  
             {
 348  1
                 str.append(child.getNodeValue());
 349  
             }
 350  33
             child = child.getNextSibling();
 351  
         }
 352  
 
 353  
         // return text value
 354  28
         return StringUtils.trimToNull(str.toString());
 355  
 
 356  
     }
 357  
 
 358  
     private Element getChildElementWithName(String eName, Element element)
 359  
     {
 360  38
         Element child = null;
 361  
 
 362  38
         NodeList list = element.getChildNodes();
 363  362
         for (int j = 0; j < list.getLength(); j++)
 364  
         {
 365  350
             Node node = list.item(j);
 366  350
             if (node instanceof Element)
 367  
             {
 368  167
                 child = (Element) node;
 369  167
                 if (eName.equals(child.getTagName()))
 370  
                 {
 371  26
                     break;
 372  
                 }
 373  141
                 child = null;
 374  
             }
 375  
         }
 376  38
         return child;
 377  
     }
 378  
 
 379  
     /**
 380  
      * Adds the property value in our document tree.
 381  
      *
 382  
      * @param name  The name of the element to set a value for.
 383  
      * @param value The value to set.
 384  
      */
 385  
     private void addXmlProperty(String name, Object value)
 386  
     {
 387  
         // parse the key
 388  23
         String[] nodes = parseElementNames(name);
 389  23
         String attName = parseAttributeName(name);
 390  
 
 391  23
         Element element = document.getDocumentElement();
 392  23
         Element parent = element;
 393  
 
 394  61
         for (int i = 0; i < nodes.length; i++)
 395  
         {
 396  39
             if (element == null)
 397  
             {
 398  1
                 break;
 399  
             }
 400  38
             parent = element;
 401  38
             String eName = nodes[i];
 402  38
             Element child = getChildElementWithName(eName, element);
 403  
 
 404  38
             element = child;
 405  
         }
 406  
 
 407  23
         Element child = document.createElement(nodes[nodes.length - 1]);
 408  23
         parent.appendChild(child);
 409  23
         if (attName == null)
 410  
         {
 411  14
             CharacterData data = document.createTextNode(String.valueOf(value));
 412  14
             child.appendChild(data);
 413  
         }
 414  
         else
 415  
         {
 416  9
             child.setAttribute(attName, String.valueOf(value));
 417  
         }
 418  23
     }
 419  
 
 420  
     /**
 421  
      * Calls super method, and also ensures the underlying {@link Document}is
 422  
      * modified so changes are persisted when saved.
 423  
      *
 424  
      * @param name The name of the property to clear.
 425  
      */
 426  
     public void clearProperty(String name)
 427  
     {
 428  21
         super.clearProperty(name);
 429  21
         clearXmlProperty(name);
 430  21
         possiblySave();
 431  21
     }
 432  
 
 433  
     private void clearXmlProperty(String name)
 434  
     {
 435  
         // parse the key
 436  21
         String[] nodes = parseElementNames(name);
 437  21
         String attName = parseAttributeName(name);
 438  
 
 439  
         // get all the matching elements
 440  21
         List children = findElementsForPropertyNodes(nodes);
 441  
 
 442  21
         if (attName == null)
 443  
         {
 444  
             // remove children with no subelements
 445  16
             Iterator cIter = children.iterator();
 446  36
             while (cIter.hasNext())
 447  
             {
 448  20
                 Element child = (Element) cIter.next();
 449  
 
 450  
                 // determine if child has subelments
 451  20
                 boolean hasSubelements = false;
 452  20
                 Node subchild = child.getFirstChild();
 453  40
                 while (subchild != null)
 454  
                 {
 455  20
                     if (subchild.getNodeType() == Node.ELEMENT_NODE)
 456  
                     {
 457  0
                         hasSubelements = true;
 458  0
                         break;
 459  
                     }
 460  20
                     subchild = subchild.getNextSibling();
 461  
                 }
 462  
 
 463  20
                 if (!hasSubelements)
 464  
                 {
 465  
                     // safe to remove
 466  20
                     if (!child.hasAttributes())
 467  
                     {
 468  
                         // remove entire node
 469  13
                         Node parent = child.getParentNode();
 470  13
                         parent.removeChild(child);
 471  
                     }
 472  
                     else
 473  
                     {
 474  
                         // only remove node contents
 475  7
                         subchild = child.getLastChild();
 476  14
                         while (subchild != null)
 477  
                         {
 478  7
                             child.removeChild(subchild);
 479  7
                             subchild = child.getLastChild();
 480  
                         }
 481  
                     }
 482  
                 }
 483  
             }
 484  
         }
 485  
         else
 486  
         {
 487  
             // remove attributes from children
 488  5
             Iterator cIter = children.iterator();
 489  16
             while (cIter.hasNext())
 490  
             {
 491  11
                 Element child = (Element) cIter.next();
 492  11
                 child.removeAttribute(attName);
 493  
             }
 494  
         }
 495  21
     }
 496  
 
 497  
     /**
 498  
      * Save the configuration if the automatic persistence is enabled and a file
 499  
      * is specified.
 500  
      */
 501  
     private void possiblySave()
 502  
     {
 503  44
         if (autoSave && fileName != null)
 504  
         {
 505  
             try
 506  
             {
 507  2
                 save();
 508  
             }
 509  0
             catch (ConfigurationException ce)
 510  
             {
 511  0
                 throw new ConfigurationRuntimeException("Failed to auto-save", ce);
 512  2
             }
 513  
         }
 514  44
     }
 515  
 
 516  
     /**
 517  
      * If true, changes are automatically persisted.
 518  
      *
 519  
      * @param autoSave
 520  
      */
 521  
     public void setAutoSave(boolean autoSave)
 522  
     {
 523  1
         this.autoSave = autoSave;
 524  1
     }
 525  
 
 526  
     /**
 527  
      * {@inheritDoc}
 528  
      */
 529  
     public void save(Writer writer) throws ConfigurationException
 530  
     {
 531  
         try
 532  
         {
 533  3
             Transformer transformer = TransformerFactory.newInstance().newTransformer();
 534  3
             Source source = new DOMSource(document);
 535  3
             Result result = new StreamResult(writer);
 536  
 
 537  3
             transformer.setOutputProperty("indent", "yes");
 538  3
             transformer.transform(source, result);
 539  
         }
 540  0
         catch (TransformerException e)
 541  
         {
 542  0
             throw new ConfigurationException(e.getMessage(), e);
 543  3
         }
 544  3
     }
 545  
 
 546  
     public String toString()
 547  
     {
 548  0
         StringWriter writer = new StringWriter();
 549  
         try
 550  
         {
 551  0
             save(writer);
 552  
         }
 553  0
         catch (ConfigurationException e)
 554  
         {
 555  0
             e.printStackTrace();
 556  0
         }
 557  0
         return writer.toString();
 558  
     }
 559  
 
 560  
     /**
 561  
      * Parse a property key and return an array of the element hierarchy it
 562  
      * specifies. For example the key "x.y.z[@abc]" will result in [x, y, z].
 563  
      *
 564  
      * @param key the key to parse
 565  
      *
 566  
      * @return the elements in the key
 567  
      */
 568  
     protected static String[] parseElementNames(String key)
 569  
     {
 570  74
         if (key == null)
 571  
         {
 572  1
             return new String[]{};
 573  
         }
 574  
         else
 575  
         {
 576  
             // find the beginning of the attribute name
 577  73
             int attStart = key.indexOf(ATTRIBUTE_START);
 578  
 
 579  73
             if (attStart > -1)
 580  
             {
 581  
                 // remove the attribute part of the key
 582  24
                 key = key.substring(0, attStart);
 583  
             }
 584  
 
 585  73
             return StringUtils.split(key, NODE_DELIMITER);
 586  
         }
 587  
     }
 588  
 
 589  
     /**
 590  
      * Parse a property key and return the attribute name if it existst.
 591  
      *
 592  
      * @param key the key to parse
 593  
      *
 594  
      * @return the attribute name, or null if the key doesn't contain one
 595  
      */
 596  
     protected static String parseAttributeName(String key)
 597  
     {
 598  75
         String name = null;
 599  
 
 600  75
         if (key != null)
 601  
         {
 602  
             // find the beginning of the attribute name
 603  74
             int attStart = key.indexOf(ATTRIBUTE_START);
 604  
 
 605  74
             if (attStart > -1)
 606  
             {
 607  
                 // find the end of the attribute name
 608  25
                 int attEnd = key.indexOf(ATTRIBUTE_END);
 609  25
                 attEnd = attEnd > -1 ? attEnd : key.length();
 610  
 
 611  25
                 name = key.substring(attStart + ATTRIBUTE_START.length(), attEnd);
 612  
             }
 613  
         }
 614  
 
 615  75
         return name;
 616  
     }
 617  
 }

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