##### Coverage Report - org.apache.commons.configuration.XMLConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
XMLConfiguration |
98% | 138/141
100% | 28/28
| 2.4;2,4
XMLConfiguration$1 |
100% | 2/2
N/A | N/A
| 2.4;2,4
XMLConfiguration$2 |
100% | 3/3
N/A | N/A
| 2.4;2,4
XMLConfiguration$XMLBuilderVisitor |
100% | 37/37
100% | 11/11
| 2.4;2,4
XMLConfiguration$XMLFileConfigurationDelegate |
100% | 3/3
N/A | N/A
| 2.4;2,4
XMLConfiguration$XMLNode |
96% | 45/47
100% | 14/14
| 2.4;2,4

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