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

Classes in this File | Line Coverage | Branch Coverage | Complexity
XMLConfiguration |
96% | 211/220
100% | 46/46
| 2.574074074074074;2,574
XMLConfiguration$1 |
100% | 2/2
N/A | N/A
| 2.574074074074074;2,574
XMLConfiguration$XMLBuilderVisitor |
100% | 41/41
100% | 13/13
| 2.574074074074074;2,574
XMLConfiguration$XMLFileConfigurationDelegate |
100% | 3/3
N/A | N/A
| 2.574074074074074;2,574
XMLConfiguration$XMLNode |
98% | 46/47
100% | 14/14
| 2.574074074074074;2,574

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.IOException;
22 |  |
     import java.io.InputStream;
23 |  |
     import java.io.Reader;
24 |  |
     import java.io.Writer;
25 |  |
     import java.net.URL;
26 |  |
     import java.net.URLConnection;
27 |  |
     import java.util.ArrayList;
28 |  |
     import java.util.Collection;
29 |  |
     import java.util.Collections;
30 |  |
     import java.util.HashMap;
31 |  |
     import java.util.Iterator;
32 |  |
     import java.util.List;
33 |  |
     import java.util.Map;
34 |  |
35 |  |
     import javax.xml.parsers.DocumentBuilder;
36 |  |
     import javax.xml.parsers.DocumentBuilderFactory;
37 |  |
     import javax.xml.parsers.ParserConfigurationException;
38 |  |
     import javax.xml.transform.OutputKeys;
39 |  |
     import javax.xml.transform.Result;
40 |  |
     import javax.xml.transform.Source;
41 |  |
     import javax.xml.transform.Transformer;
42 |  |
     import javax.xml.transform.TransformerException;
43 |  |
     import javax.xml.transform.TransformerFactory;
44 |  |
     import javax.xml.transform.TransformerFactoryConfigurationError;
45 |  |
     import javax.xml.transform.dom.DOMSource;
46 |  |
     import javax.xml.transform.stream.StreamResult;
47 |  |
48 |  |
     import org.apache.commons.configuration.tree.ConfigurationNode;
49 |  |
     import org.w3c.dom.Attr;
50 |  |
     import org.w3c.dom.CDATASection;
51 |  |
     import org.w3c.dom.DOMException;
52 |  |
     import org.w3c.dom.Document;
53 |  |
     import org.w3c.dom.Element;
54 |  |
     import org.w3c.dom.NamedNodeMap;
55 |  |
     import org.w3c.dom.NodeList;
56 |  |
     import org.w3c.dom.Text;
57 |  |
     import org.xml.sax.EntityResolver;
58 |  |
     import org.xml.sax.InputSource;
59 |  |
     import org.xml.sax.SAXException;
60 |  |
     import org.xml.sax.SAXParseException;
61 |  |
     import org.xml.sax.helpers.DefaultHandler;
62 |  |
63 |  |
     /**
64 |  |
      * <p>A specialized hierarchical configuration class that is able to parse XML
65 |  |
      * documents.</p>
66 |  |
      *
67 |  |
      * <p>The parsed document will be stored keeping its structure. The class also
68 |  |
      * tries to preserve as much information from the loaded XML document as
69 |  |
      * possible, including comments and processing instructions. These will be
70 |  |
      * contained in documents created by the <code>save()</code> methods, too.</p>
71 |  |
      *
72 |  |
      * <p>Like other file based configuration classes this class maintains the name
73 |  |
      * and path to the loaded configuration file. These properties can be altered
74 |  |
      * using several setter methods, but they are not modified by <code>save()</code>
75 |  |
      * and <code>load()</code> methods. If XML documents contain relative paths to
76 |  |
      * other documents (e.g. to a DTD), these references are resolved based on the
77 |  |
      * path set for this configuration.</p>
78 |  |
      *
79 |  |
      * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
80 |  |
      * provides some extended functionality, e.g. interpolation of property values.
81 |  |
      * Like in <code>{@link PropertiesConfiguration}</code> property values can
82 |  |
      * contain delimiter characters (the comma ',' per default) and are then split
83 |  |
      * into multiple values. This works for XML attributes and text content of
84 |  |
      * elements as well. The delimiter can be escaped by a backslash. As an example
85 |  |
      * consider the following XML fragment:</p>
86 |  |
      *
87 |  |
      * <p>
88 |  |
      * <pre>
89 |  |
      * &lt;config&gt;
90 |  |
      *   &lt;array&gt;10,20,30,40&lt;/array&gt;
91 |  |
      *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
92 |  |
      *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
93 |  |
      * &lt;/config&gt;
94 |  |
      * </pre>
95 |  |
      * </p>
96 |  |
      * <p>Here the content of the <code>array</code> element will be split at
97 |  |
      * the commas, so the <code>array</code> key will be assigned 4 values. In the
98 |  |
      * <code>scalar</code> property and the <code>text</code> attribute of the
99 |  |
      * <code>cite</code> element the comma is escaped, so that no splitting is
100 |  |
      * performed.</p>
101 |  |
      *
102 |  |
      * <p>The configuration API allows setting multiple values for a single attribute,
103 |  |
      * e.g. something like the following is legal (assuming that the default
104 |  |
      * expression engine is used):
105 |  |
      * <pre>
106 |  |
      * XMLConfiguration config = new XMLConfiguration();
107 |  |
      * config.addProperty("test.dir[@name]", "C:\\Temp\\");
108 |  |
      * config.addProperty("test.dir[@name]", "D:\\Data\\");
109 |  |
      * </pre></p>
110 |  |
      *
111 |  |
      * <p>Because in XML such a constellation is not directly supported (an attribute
112 |  |
      * can appear only once for a single element), the values are concatenated to a
113 |  |
      * single value. If delimiter parsing is enabled (refer to the
114 |  |
      * <code>{@link #setDelimiterParsingDisabled(boolean)}</code> method), the
115 |  |
      * current list delimiter character will be used as separator. Otherwise the
116 |  |
      * pipe symbol ("|") will be used for this purpose. No matter which character is
117 |  |
      * used as delimiter, it can always be escaped with a backslash. A backslash
118 |  |
      * itself can also be escaped with another backslash. Consider the following
119 |  |
      * example fragment from a configuration file:
120 |  |
      * <pre>
121 |  |
      * &lt;directories names="C:\Temp\\|D:\Data\"/&gt;
122 |  |
      * </pre>
123 |  |
      * Here the backslash after Temp is escaped. This is necessary because it
124 |  |
      * would escape the list delimiter (the pipe symbol assuming that list delimiter
125 |  |
      * parsing is disabled) otherwise. So this attribute would have two values.</p>
126 |  |
      *
127 |  |
      * <p>Note: You should ensure that the <em>delimiter parsing disabled</em>
128 |  |
      * property is always consistent when you load and save a configuration file.
129 |  |
      * Otherwise the values of properties can become corrupted.</p>
130 |  |
      *
131 |  |
      * <p>Whitespace in the content of XML documents is trimmed per default. In most
132 |  |
      * cases this is desired. However, sometimes whitespace is indeed important and
133 |  |
      * should be treated as part of the value of a property as in the following
134 |  |
      * example:
135 |  |
      * <pre>
136 |  |
      *   &lt;indent&gt;    &lt;/indent&gt;
137 |  |
      * </pre></p>
138 |  |
      *
139 |  |
      * <p>Per default the spaces in the <code>indent</code> element will be trimmed
140 |  |
      * resulting in an empty element. To tell <code>XMLConfiguration</code> that
141 |  |
      * spaces are relevant the <code>xml:space</code> attribute can be used, which is
142 |  |
      * defined in the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
143 |  |
      * specification</a>. This will look as follows:
144 |  |
      * <pre>
145 |  |
      *   &lt;indent <strong>xml:space=&quot;preserve&quot;</strong>&gt;    &lt;/indent&gt;
146 |  |
      * </pre>
147 |  |
      * The value of the <code>indent</code> property will now contain the spaces.</p>
148 |  |
      *
149 |  |
      * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
150 |  |
      * interface and thus provides full support for loading XML documents from
151 |  |
      * different sources like files, URLs, or streams. A full description of these
152 |  |
      * features can be found in the documentation of
153 |  |
      * <code>{@link AbstractFileConfiguration}</code>.</p>
154 |  |
      *
155 |  |
      * <p><em>Note:</em>Configuration objects of this type can be read concurrently
156 |  |
      * by multiple threads. However if one of these threads modifies the object,
157 |  |
      * synchronization has to be performed manually.</p>
158 |  |
      *
159 |  |
      * @since commons-configuration 1.0
160 |  |
      *
161 |  |
      * @author J&ouml;rg Schaible
162 |  |
      * @author Oliver Heger
163 |  |
      * @version $Revision: 721895 $, $Date: 2008-11-30 22:08:42 +0100 (So, 30 Nov 2008) $
164 |  |
      */
165 | 40864 |
     public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
166 |  |
         implements EntityResolver
167 |  |
     {
168 |  |
         /**
169 |  |
          * The serial version UID.
170 |  |
          */
171 |  |
         private static final long serialVersionUID = 2453781111653383552L;
172 |  |
173 |  |
         /** Constant for the default root element name. */
174 |  |
         private static final String DEFAULT_ROOT_NAME = "configuration";
175 |  |
176 |  |
         /** Constant for the name of the space attribute.*/
177 |  |
         private static final String ATTR_SPACE = "xml:space";
178 |  |
179 |  |
         /** Constant for the xml:space value for preserving whitespace.*/
180 |  |
         private static final String VALUE_PRESERVE = "preserve";
181 |  |
182 |  |
         /** Constant for the delimiter for multiple attribute values.*/
183 |  |
         private static final char ATTR_VALUE_DELIMITER = '|';
184 |  |
185 |  |
         /** The document from this configuration's data source. */
186 |  |
         private Document document;
187 |  |
188 |  |
         /** Stores a map with the registered public IDs.*/
189 | 424 |
         private Map registeredEntities = new HashMap();
190 |  |
191 |  |
         /** Stores the name of the root element. */
192 |  |
         private String rootElementName;
193 |  |
194 |  |
         /** Stores the public ID from the DOCTYPE.*/
195 |  |
         private String publicID;
196 |  |
197 |  |
         /** Stores the system ID from the DOCTYPE.*/
198 |  |
         private String systemID;
199 |  |
200 |  |
         /** Stores the document builder that should be used for loading.*/
201 |  |
         private DocumentBuilder documentBuilder;
202 |  |
203 |  |
         /** Stores a flag whether DTD validation should be performed.*/
204 |  |
         private boolean validating;
205 |  |
206 |  |
         /** A flag whether attribute splitting is disabled.*/
207 |  |
         private boolean attributeSplittingDisabled;
208 |  |
209 |  |
         /**
210 |  |
          * Creates a new instance of <code>XMLConfiguration</code>.
211 |  |
          */
212 |  |
         public XMLConfiguration()
213 |  |
         {
214 | 293 |
             super();
215 | 293 |
         }
216 |  |
217 |  |
         /**
218 |  |
          * Creates a new instance of <code>XMLConfiguration</code> and copies the
219 |  |
          * content of the passed in configuration into this object. Note that only
220 |  |
          * the data of the passed in configuration will be copied. If, for instance,
221 |  |
          * the other configuration is a <code>XMLConfiguration</code>, too,
222 |  |
          * things like comments or processing instructions will be lost.
223 |  |
          *
224 |  |
          * @param c the configuration to copy
225 |  |
          * @since 1.4
226 |  |
          */
227 |  |
         public XMLConfiguration(HierarchicalConfiguration c)
228 |  |
         {
229 | 9 |
             super(c);
230 | 9 |
             clearReferences(getRootNode());
231 | 9 |
             setRootElementName(getRootNode().getName());
232 | 9 |
         }
233 |  |
234 |  |
         /**
235 |  |
          * Creates a new instance of <code>XMLConfiguration</code>. The
236 |  |
          * configuration is loaded from the specified file
237 |  |
          *
238 |  |
          * @param fileName the name of the file to load
239 |  |
          * @throws ConfigurationException if the file cannot be loaded
240 |  |
          */
241 |  |
         public XMLConfiguration(String fileName) throws ConfigurationException
242 |  |
         {
243 | 5 |
             super(fileName);
244 | 5 |
         }
245 |  |
246 |  |
         /**
247 |  |
          * Creates a new instance of <code>XMLConfiguration</code>.
248 |  |
          * The configuration is loaded from the specified file.
249 |  |
          *
250 |  |
          * @param file the file
251 |  |
          * @throws ConfigurationException if an error occurs while loading the file
252 |  |
          */
253 |  |
         public XMLConfiguration(File file) throws ConfigurationException
254 |  |
         {
255 | 116 |
             super(file);
256 | 116 |
         }
257 |  |
258 |  |
         /**
259 |  |
          * Creates a new instance of <code>XMLConfiguration</code>.
260 |  |
          * The configuration is loaded from the specified URL.
261 |  |
          *
262 |  |
          * @param url the URL
263 |  |
          * @throws ConfigurationException if loading causes an error
264 |  |
          */
265 |  |
         public XMLConfiguration(URL url) throws ConfigurationException
266 |  |
         {
267 | 1 |
             super(url);
268 | 1 |
         }
269 |  |
270 |  |
         /**
271 |  |
          * Returns the name of the root element. If this configuration was loaded
272 |  |
          * from a XML document, the name of this document's root element is
273 |  |
          * returned. Otherwise it is possible to set a name for the root element
274 |  |
          * that will be used when this configuration is stored.
275 |  |
          *
276 |  |
          * @return the name of the root element
277 |  |
          */
278 |  |
         public String getRootElementName()
279 |  |
         {
280 | 24 |
             if (getDocument() == null)
281 |  |
             {
282 | 19 |
                 return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
283 |  |
             }
284 |  |
             else
285 |  |
             {
286 | 5 |
                 return getDocument().getDocumentElement().getNodeName();
287 |  |
             }
288 |  |
         }
289 |  |
290 |  |
         /**
291 |  |
          * Sets the name of the root element. This name is used when this
292 |  |
          * configuration object is stored in an XML file. Note that setting the name
293 |  |
          * of the root element works only if this configuration has been newly
294 |  |
          * created. If the configuration was loaded from an XML file, the name
295 |  |
          * cannot be changed and an <code>UnsupportedOperationException</code>
296 |  |
          * exception is thrown. Whether this configuration has been loaded from an
297 |  |
          * XML document or not can be found out using the <code>getDocument()</code>
298 |  |
          * method.
299 |  |
          *
300 |  |
          * @param name the name of the root element
301 |  |
          */
302 |  |
         public void setRootElementName(String name)
303 |  |
         {
304 | 13 |
             if (getDocument() != null)
305 |  |
             {
306 | 1 |
                 throw new UnsupportedOperationException("The name of the root element "
307 |  |
                         + "cannot be changed when loaded from an XML document!");
308 |  |
             }
309 | 12 |
             rootElementName = name;
310 | 12 |
             getRootNode().setName(name);
311 | 12 |
         }
312 |  |
313 |  |
         /**
314 |  |
          * Returns the <code>DocumentBuilder</code> object that is used for
315 |  |
          * loading documents. If no specific builder has been set, this method
316 |  |
          * returns <b>null</b>.
317 |  |
          *
318 |  |
          * @return the <code>DocumentBuilder</code> for loading new documents
319 |  |
          * @since 1.2
320 |  |
          */
321 |  |
         public DocumentBuilder getDocumentBuilder()
322 |  |
         {
323 | 462 |
             return documentBuilder;
324 |  |
         }
325 |  |
326 |  |
         /**
327 |  |
          * Sets the <code>DocumentBuilder</code> object to be used for loading
328 |  |
          * documents. This method makes it possible to specify the exact document
329 |  |
          * builder. So an application can create a builder, configure it for its
330 |  |
          * special needs, and then pass it to this method.
331 |  |
          *
332 |  |
          * @param documentBuilder the document builder to be used; if undefined, a
333 |  |
          * default builder will be used
334 |  |
          * @since 1.2
335 |  |
          */
336 |  |
         public void setDocumentBuilder(DocumentBuilder documentBuilder)
337 |  |
         {
338 | 2 |
             this.documentBuilder = documentBuilder;
339 | 2 |
         }
340 |  |
341 |  |
         /**
342 |  |
          * Returns the public ID of the DOCTYPE declaration from the loaded XML
343 |  |
          * document. This is <b>null</b> if no document has been loaded yet or if
344 |  |
          * the document does not contain a DOCTYPE declaration with a public ID.
345 |  |
          *
346 |  |
          * @return the public ID
347 |  |
          * @since 1.3
348 |  |
          */
349 |  |
         public String getPublicID()
350 |  |
         {
351 | 68 |
             return publicID;
352 |  |
         }
353 |  |
354 |  |
         /**
355 |  |
          * Sets the public ID of the DOCTYPE declaration. When this configuration is
356 |  |
          * saved, a DOCTYPE declaration will be constructed that contains this
357 |  |
          * public ID.
358 |  |
          *
359 |  |
          * @param publicID the public ID
360 |  |
          * @since 1.3
361 |  |
          */
362 |  |
         public void setPublicID(String publicID)
363 |  |
         {
364 | 13 |
             this.publicID = publicID;
365 | 13 |
         }
366 |  |
367 |  |
         /**
368 |  |
          * Returns the system ID of the DOCTYPE declaration from the loaded XML
369 |  |
          * document. This is <b>null</b> if no document has been loaded yet or if
370 |  |
          * the document does not contain a DOCTYPE declaration with a system ID.
371 |  |
          *
372 |  |
          * @return the system ID
373 |  |
          * @since 1.3
374 |  |
          */
375 |  |
         public String getSystemID()
376 |  |
         {
377 | 68 |
             return systemID;
378 |  |
         }
379 |  |
380 |  |
         /**
381 |  |
          * Sets the system ID of the DOCTYPE declaration. When this configuration is
382 |  |
          * saved, a DOCTYPE declaration will be constructed that contains this
383 |  |
          * system ID.
384 |  |
          *
385 |  |
          * @param systemID the system ID
386 |  |
          * @since 1.3
387 |  |
          */
388 |  |
         public void setSystemID(String systemID)
389 |  |
         {
390 | 11 |
             this.systemID = systemID;
391 | 11 |
         }
392 |  |
393 |  |
         /**
394 |  |
          * Returns the value of the validating flag.
395 |  |
          *
396 |  |
          * @return the validating flag
397 |  |
          * @since 1.2
398 |  |
          */
399 |  |
         public boolean isValidating()
400 |  |
         {
401 | 917 |
             return validating;
402 |  |
         }
403 |  |
404 |  |
         /**
405 |  |
          * Sets the value of the validating flag. This flag determines whether
406 |  |
          * DTD validation should be performed when loading XML documents. This
407 |  |
          * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
408 |  |
          *
409 |  |
          * @param validating the validating flag
410 |  |
          * @since 1.2
411 |  |
          */
412 |  |
         public void setValidating(boolean validating)
413 |  |
         {
414 | 4 |
             this.validating = validating;
415 | 4 |
         }
416 |  |
417 |  |
         /**
418 |  |
          * Returns the flag whether attribute splitting is disabled.
419 |  |
          *
420 |  |
          * @return the flag whether attribute splitting is disabled
421 |  |
          * @see #setAttributeSplittingDisabled(boolean)
422 |  |
          * @since 1.6
423 |  |
          */
424 |  |
         public boolean isAttributeSplittingDisabled()
425 |  |
         {
426 | 6709 |
             return attributeSplittingDisabled;
427 |  |
         }
428 |  |
429 |  |
         /**
430 |  |
          * <p>
431 |  |
          * Sets a flag whether attribute splitting is disabled.
432 |  |
          * </p>
433 |  |
          * <p>
434 |  |
          * The Configuration API allows adding multiple values to an attribute. This
435 |  |
          * is problematic when storing the configuration because in XML an attribute
436 |  |
          * can appear only once with a single value. To solve this problem, per
437 |  |
          * default multiple attribute values are concatenated using a special
438 |  |
          * separator character and split again when the configuration is loaded. The
439 |  |
          * separator character is either the list delimiter character (see
440 |  |
          * {@link #setListDelimiter(char)}) or the pipe symbol (&quot;|&quot;) if
441 |  |
          * list delimiter parsing is disabled.
442 |  |
          * </p>
443 |  |
          * <p>
444 |  |
          * In some constellations the splitting of attribute values can have
445 |  |
          * undesired effects, especially if list delimiter parsing is disabled and
446 |  |
          * attributes may contain the &quot;|&quot; character. In these cases it is
447 |  |
          * possible to disable the attribute splitting mechanism by calling this
448 |  |
          * method with a boolean value set to <b>false</b>. If attribute splitting
449 |  |
          * is disabled, the values of attributes will not be processed, but stored
450 |  |
          * as configuration properties exactly as they are returned by the XML
451 |  |
          * parser.
452 |  |
          * </p>
453 |  |
          * <p>
454 |  |
          * Note that in this mode multiple attribute values cannot be handled
455 |  |
          * correctly. It is possible to create a <code>XMLConfiguration</code>
456 |  |
          * object, add multiple values to an attribute and save it. When the
457 |  |
          * configuration is loaded again and attribute splitting is disabled, the
458 |  |
          * attribute will only have a single value, which is the concatenation of
459 |  |
          * all values set before. So it lies in the responsibility of the
460 |  |
          * application to carefully set the values of attributes.
461 |  |
          * </p>
462 |  |
          * <p>
463 |  |
          * As is true for the {@link #setDelimiterParsingDisabled(boolean)} method,
464 |  |
          * this method must be called before the configuration is loaded. So it
465 |  |
          * can't be used together with one of the constructors expecting the
466 |  |
          * specification of the file to load. Instead the default constructor has to
467 |  |
          * be used, then <code>setAttributeSplittingDisabled(false)</code> has to be
468 |  |
          * called, and finally the configuration can be loaded using one of its
469 |  |
          * <code>load()</code> methods.
470 |  |
          * </p>
471 |  |
          *
472 |  |
          * @param attributeSplittingDisabled <b>true</b> for disabling attribute
473 |  |
          *        splitting, <b>false</b> for enabling it
474 |  |
          * @see #setDelimiterParsingDisabled(boolean)
475 |  |
          * @since 1.6
476 |  |
          */
477 |  |
         public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
478 |  |
         {
479 | 2 |
             this.attributeSplittingDisabled = attributeSplittingDisabled;
480 | 2 |
         }
481 |  |
482 |  |
         /**
483 |  |
          * Returns the XML document this configuration was loaded from. The return
484 |  |
          * value is <b>null</b> if this configuration was not loaded from a XML
485 |  |
          * document.
486 |  |
          *
487 |  |
          * @return the XML document this configuration was loaded from
488 |  |
          */
489 |  |
         public Document getDocument()
490 |  |
         {
491 | 45 |
             return document;
492 |  |
         }
493 |  |
494 |  |
         /**
495 |  |
          * Removes all properties from this configuration. If this configuration
496 |  |
          * was loaded from a file, the associated DOM document is also cleared.
497 |  |
          */
498 |  |
         public void clear()
499 |  |
         {
500 | 129 |
             super.clear();
501 | 129 |
             document = null;
502 | 129 |
         }
503 |  |
504 |  |
         /**
505 |  |
          * Initializes this configuration from an XML document.
506 |  |
          *
507 |  |
          * @param document the document to be parsed
508 |  |
          * @param elemRefs a flag whether references to the XML elements should be set
509 |  |
          */
510 |  |
         public void initProperties(Document document, boolean elemRefs)
511 |  |
         {
512 | 456 |
             if (document.getDoctype() != null)
513 |  |
             {
514 | 10 |
                 setPublicID(document.getDoctype().getPublicId());
515 | 10 |
                 setSystemID(document.getDoctype().getSystemId());
516 |  |
             }
517 |  |
518 | 456 |
             constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs, true);
519 | 456 |
             getRootNode().setName(document.getDocumentElement().getNodeName());
520 | 456 |
             if (elemRefs)
521 |  |
             {
522 | 446 |
                 getRoot().setReference(document.getDocumentElement());
523 |  |
             }
524 | 456 |
         }
525 |  |
526 |  |
         /**
527 |  |
          * Helper method for building the internal storage hierarchy. The XML
528 |  |
          * elements are transformed into node objects.
529 |  |
          *
530 |  |
          * @param node the actual node
531 |  |
          * @param element the actual XML element
532 |  |
          * @param elemRefs a flag whether references to the XML elements should be set
533 |  |
          * @param trim a flag whether the text content of elements should be trimmed;
534 |  |
          * this controls the whitespace handling
535 |  |
          */
536 |  |
         private void constructHierarchy(Node node, Element element, boolean elemRefs, boolean trim)
537 |  |
         {
538 | 20861 |
             boolean trimFlag = shouldTrim(element, trim);
539 | 20861 |
             processAttributes(node, element, elemRefs);
540 | 20861 |
             StringBuffer buffer = new StringBuffer();
541 | 20861 |
             NodeList list = element.getChildNodes();
542 | 86663 |
             for (int i = 0; i < list.getLength(); i++)
543 |  |
             {
544 | 65802 |
                 org.w3c.dom.Node w3cNode = list.item(i);
545 | 65802 |
                 if (w3cNode instanceof Element)
546 |  |
                 {
547 | 20405 |
                     Element child = (Element) w3cNode;
548 | 20405 |
                     Node childNode = new XMLNode(child.getTagName(),
549 |  |
                             elemRefs ? child : null);
550 | 20405 |
                     constructHierarchy(childNode, child, elemRefs, trimFlag);
551 | 20405 |
                     node.addChild(childNode);
552 | 20405 |
                     handleDelimiters(node, childNode, trimFlag);
553 |  |
                 }
554 | 45397 |
                 else if (w3cNode instanceof Text)
555 |  |
                 {
556 | 41962 |
                     Text data = (Text) w3cNode;
557 | 41962 |
                     buffer.append(data.getData());
558 |  |
                 }
559 |  |
             }
560 |  |
561 | 20861 |
             String text = buffer.toString();
562 | 20861 |
             if (trimFlag)
563 |  |
             {
564 | 19922 |
                 text = text.trim();
565 |  |
             }
566 | 20861 |
             if (text.length() > 0 || !node.hasChildren())
567 |  |
             {
568 | 14088 |
                 node.setValue(text);
569 |  |
             }
570 | 20861 |
         }
571 |  |
572 |  |
         /**
573 |  |
          * Helper method for constructing node objects for the attributes of the
574 |  |
          * given XML element.
575 |  |
          *
576 |  |
          * @param node the actual node
577 |  |
          * @param element the actual XML element
578 |  |
          * @param elemRefs a flag whether references to the XML elements should be set
579 |  |
          */
580 |  |
         private void processAttributes(Node node, Element element, boolean elemRefs)
581 |  |
         {
582 | 20861 |
             NamedNodeMap attributes = element.getAttributes();
583 | 27570 |
             for (int i = 0; i < attributes.getLength(); ++i)
584 |  |
             {
585 | 6709 |
                 org.w3c.dom.Node w3cNode = attributes.item(i);
586 | 6709 |
                 if (w3cNode instanceof Attr)
587 |  |
                 {
588 | 6709 |
                     Attr attr = (Attr) w3cNode;
589 |  |
                     List values;
590 | 6709 |
                     if (isAttributeSplittingDisabled())
591 |  |
                     {
592 | 40 |
                         values = Collections.singletonList(attr.getValue());
593 |  |
                     }
594 |  |
                     else
595 |  |
                     {
596 | 6669 |
                         values = PropertyConverter.split(attr.getValue(),
597 |  |
                                 isDelimiterParsingDisabled() ? ATTR_VALUE_DELIMITER
598 |  |
                                         : getListDelimiter());
599 |  |
                     }
600 |  |
601 | 6709 |
                     for (Iterator it = values.iterator(); it.hasNext();)
602 |  |
                     {
603 | 8249 |
                         Node child = new XMLNode(attr.getName(), elemRefs ? element
604 |  |
                                 : null);
605 | 8249 |
                         child.setValue(it.next());
606 | 8249 |
                         node.addAttribute(child);
607 |  |
                     }
608 |  |
                 }
609 |  |
             }
610 | 20861 |
         }
611 |  |
612 |  |
         /**
613 |  |
          * Deals with elements whose value is a list. In this case multiple child
614 |  |
          * elements must be added.
615 |  |
          *
616 |  |
          * @param parent the parent element
617 |  |
          * @param child the child element
618 |  |
          * @param trim flag whether texts of elements should be trimmed
619 |  |
          */
620 |  |
         private void handleDelimiters(Node parent, Node child, boolean trim)
621 |  |
         {
622 | 20405 |
             if (child.getValue() != null)
623 |  |
             {
624 |  |
                 List values;
625 | 14086 |
                 if (isDelimiterParsingDisabled())
626 |  |
                 {
627 | 322 |
                     values = new ArrayList();
628 | 322 |
                     values.add(child.getValue().toString());
629 |  |
                 }
630 |  |
                 else
631 |  |
                 {
632 | 13764 |
                     values = PropertyConverter.split(child.getValue().toString(),
633 |  |
                         getListDelimiter(), trim);
634 |  |
                 }
635 |  |
636 | 14086 |
                 if (values.size() > 1)
637 |  |
                 {
638 | 830 |
                     Iterator it = values.iterator();
639 |  |
                     // Create new node for the original child's first value
640 | 830 |
                     Node c = createNode(child.getName());
641 | 830 |
                     c.setValue(it.next());
642 |  |
                     // Copy original attributes to the new node
643 | 830 |
                     for (Iterator itAttrs = child.getAttributes().iterator(); itAttrs
644 | 2193 |
                             .hasNext();)
645 |  |
                     {
646 | 1363 |
                         Node ndAttr = (Node) itAttrs.next();
647 | 1363 |
                         ndAttr.setReference(null);
648 | 1363 |
                         c.addAttribute(ndAttr);
649 |  |
                     }
650 | 830 |
                     parent.remove(child);
651 | 830 |
                     parent.addChild(c);
652 |  |
653 |  |
                     // add multiple new children
654 | 2217 |
                     while (it.hasNext())
655 |  |
                     {
656 | 1387 |
                         c = new XMLNode(child.getName(), null);
657 | 1387 |
                         c.setValue(it.next());
658 | 1387 |
                         parent.addChild(c);
659 |  |
                     }
660 |  |
                 }
661 | 13256 |
                 else if (values.size() == 1)
662 |  |
                 {
663 |  |
                     // we will have to replace the value because it might
664 |  |
                     // contain escaped delimiters
665 | 13256 |
                     child.setValue(values.get(0));
666 |  |
                 }
667 |  |
             }
668 | 20405 |
         }
669 |  |
670 |  |
         /**
671 |  |
          * Checks whether the content of the current XML element should be trimmed.
672 |  |
          * This method checks whether a <code>xml:space</code> attribute is
673 |  |
          * present and evaluates its value. See <a
674 |  |
          * href="http://www.w3.org/TR/REC-xml/#sec-white-space">
675 |  |
          * http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
676 |  |
          *
677 |  |
          * @param element the current XML element
678 |  |
          * @param currentTrim the current trim flag
679 |  |
          * @return a flag whether the content of this element should be trimmed
680 |  |
          */
681 |  |
         private boolean shouldTrim(Element element, boolean currentTrim)
682 |  |
         {
683 | 20861 |
             Attr attr = element.getAttributeNode(ATTR_SPACE);
684 |  |
685 | 20861 |
             if (attr == null)
686 |  |
             {
687 | 19922 |
                 return currentTrim;
688 |  |
             }
689 |  |
             else
690 |  |
             {
691 | 939 |
                 return !VALUE_PRESERVE.equals(attr.getValue());
692 |  |
             }
693 |  |
         }
694 |  |
695 |  |
         /**
696 |  |
          * Creates the <code>DocumentBuilder</code> to be used for loading files.
697 |  |
          * This implementation checks whether a specific
698 |  |
          * <code>DocumentBuilder</code> has been set. If this is the case, this
699 |  |
          * one is used. Otherwise a default builder is created. Depending on the
700 |  |
          * value of the validating flag this builder will be a validating or a non
701 |  |
          * validating <code>DocumentBuilder</code>.
702 |  |
          *
703 |  |
          * @return the <code>DocumentBuilder</code> for loading configuration
704 |  |
          * files
705 |  |
          * @throws ParserConfigurationException if an error occurs
706 |  |
          * @since 1.2
707 |  |
          */
708 |  |
         protected DocumentBuilder createDocumentBuilder()
709 |  |
                 throws ParserConfigurationException
710 |  |
         {
711 | 460 |
             if (getDocumentBuilder() != null)
712 |  |
             {
713 | 2 |
                 return getDocumentBuilder();
714 |  |
             }
715 |  |
             else
716 |  |
             {
717 | 458 |
                 DocumentBuilderFactory factory = DocumentBuilderFactory
718 |  |
                         .newInstance();
719 | 458 |
                 factory.setValidating(isValidating());
720 | 458 |
                 DocumentBuilder result = factory.newDocumentBuilder();
721 | 458 |
                 result.setEntityResolver(this);
722 |  |
723 | 458 |
                 if (isValidating())
724 |  |
                 {
725 |  |
                     // register an error handler which detects validation errors
726 | 4 |
                     result.setErrorHandler(new DefaultHandler()
727 |  |
                     {
728 | 4 |
                         public void error(SAXParseException ex) throws SAXException
729 |  |
                         {
730 | 2 |
                             throw ex;
731 |  |
                         }
732 |  |
                     });
733 |  |
                 }
734 | 458 |
                 return result;
735 |  |
             }
736 |  |
         }
737 |  |
738 |  |
         /**
739 |  |
          * Creates a DOM document from the internal tree of configuration nodes.
740 |  |
          *
741 |  |
          * @return the new document
742 |  |
          * @throws ConfigurationException if an error occurs
743 |  |
          */
744 |  |
         protected Document createDocument() throws ConfigurationException
745 |  |
         {
746 |  |
             try
747 |  |
             {
748 | 62 |
                 if (document == null)
749 |  |
                 {
750 | 15 |
                     DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
751 | 15 |
                     Document newDocument = builder.newDocument();
752 | 15 |
                     Element rootElem = newDocument.createElement(getRootElementName());
753 | 15 |
                     newDocument.appendChild(rootElem);
754 | 15 |
                     document = newDocument;
755 |  |
                 }
756 |  |
757 | 62 |
                 XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
758 |  |
                         isDelimiterParsingDisabled() ? (char) 0 : getListDelimiter());
759 | 62 |
                 builder.processDocument(getRoot());
760 | 62 |
                 initRootElementText(document, getRootNode().getValue());
761 | 62 |
                 return document;
762 |  |
             }
763 | 0 |
             catch (DOMException domEx)
764 |  |
             {
765 | 0 |
                 throw new ConfigurationException(domEx);
766 |  |
             }
767 | 0 |
             catch (ParserConfigurationException pex)
768 |  |
             {
769 | 0 |
                 throw new ConfigurationException(pex);
770 |  |
             }
771 |  |
         }
772 |  |
773 |  |
         /**
774 |  |
          * Sets the text of the root element of a newly created XML Document.
775 |  |
          *
776 |  |
          * @param doc the document
777 |  |
          * @param value the new text to be set
778 |  |
          */
779 |  |
         private void initRootElementText(Document doc, Object value)
780 |  |
         {
781 | 62 |
             Element elem = doc.getDocumentElement();
782 | 62 |
             NodeList children = elem.getChildNodes();
783 |  |
784 |  |
             // Remove all existing text nodes
785 | 1044 |
             for (int i = 0; i < children.getLength(); i++)
786 |  |
             {
787 | 982 |
                 org.w3c.dom.Node nd = children.item(i);
788 | 982 |
                 if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
789 |  |
                 {
790 | 669 |
                     elem.removeChild(nd);
791 |  |
                 }
792 |  |
             }
793 |  |
794 | 62 |
             if (value != null)
795 |  |
             {
796 |  |
                 // Add a new text node
797 | 1 |
                 elem.appendChild(doc.createTextNode(String.valueOf(value)));
798 |  |
             }
799 | 62 |
         }
800 |  |
801 |  |
         /**
802 |  |
          * Creates a new node object. This implementation returns an instance of the
803 |  |
          * <code>XMLNode</code> class.
804 |  |
          *
805 |  |
          * @param name the node's name
806 |  |
          * @return the new node
807 |  |
          */
808 |  |
         protected Node createNode(String name)
809 |  |
         {
810 | 4153 |
             return new XMLNode(name, null);
811 |  |
         }
812 |  |
813 |  |
         /**
814 |  |
          * Loads the configuration from the given input stream.
815 |  |
          *
816 |  |
          * @param in the input stream
817 |  |
          * @throws ConfigurationException if an error occurs
818 |  |
          */
819 |  |
         public void load(InputStream in) throws ConfigurationException
820 |  |
         {
821 | 455 |
             load(new InputSource(in));
822 | 452 |
         }
823 |  |
824 |  |
         /**
825 |  |
          * Load the configuration from the given reader.
826 |  |
          * Note that the <code>clear()</code> method is not called, so
827 |  |
          * the properties contained in the loaded file will be added to the
828 |  |
          * actual set of properties.
829 |  |
          *
830 |  |
          * @param in An InputStream.
831 |  |
          *
832 |  |
          * @throws ConfigurationException if an error occurs
833 |  |
          */
834 |  |
         public void load(Reader in) throws ConfigurationException
835 |  |
         {
836 | 5 |
             load(new InputSource(in));
837 | 4 |
         }
838 |  |
839 |  |
         /**
840 |  |
          * Loads a configuration file from the specified input source.
841 |  |
          * @param source the input source
842 |  |
          * @throws ConfigurationException if an error occurs
843 |  |
          */
844 |  |
         private void load(InputSource source) throws ConfigurationException
845 |  |
         {
846 |  |
             try
847 |  |
             {
848 | 460 |
                 URL sourceURL = getDelegate().getURL();
849 | 460 |
                 if (sourceURL != null)
850 |  |
                 {
851 | 457 |
                     source.setSystemId(sourceURL.toString());
852 |  |
                 }
853 |  |
854 | 460 |
                 DocumentBuilder builder = createDocumentBuilder();
855 | 460 |
                 Document newDocument = builder.parse(source);
856 | 456 |
                 Document oldDocument = document;
857 | 456 |
                 document = null;
858 | 456 |
                 initProperties(newDocument, oldDocument == null);
859 | 456 |
                 document = (oldDocument == null) ? newDocument : oldDocument;
860 |  |
             }
861 | 4 |
             catch (Exception e)
862 |  |
             {
863 | 4 |
                 throw new ConfigurationException("Unable to load the configuration", e);
864 | 456 |
             }
865 | 456 |
         }
866 |  |
867 |  |
         /**
868 |  |
          * Saves the configuration to the specified writer.
869 |  |
          *
870 |  |
          * @param writer the writer used to save the configuration
871 |  |
          * @throws ConfigurationException if an error occurs
872 |  |
          */
873 |  |
         public void save(Writer writer) throws ConfigurationException
874 |  |
         {
875 |  |
             try
876 |  |
             {
877 | 63 |
                 Transformer transformer = createTransformer();
878 | 62 |
                 Source source = new DOMSource(createDocument());
879 | 62 |
                 Result result = new StreamResult(writer);
880 | 62 |
                 transformer.transform(source, result);
881 |  |
             }
882 | 0 |
             catch (TransformerException e)
883 |  |
             {
884 | 0 |
                 throw new ConfigurationException("Unable to save the configuration", e);
885 |  |
             }
886 | 1 |
             catch (TransformerFactoryConfigurationError e)
887 |  |
             {
888 | 1 |
                 throw new ConfigurationException("Unable to save the configuration", e);
889 | 62 |
             }
890 | 62 |
         }
891 |  |
892 |  |
         /**
893 |  |
          * Creates and initializes the transformer used for save operations. This
894 |  |
          * base implementation initializes all of the default settings like
895 |  |
          * indention mode and the DOCTYPE. Derived classes may overload this method
896 |  |
          * if they have specific needs.
897 |  |
          *
898 |  |
          * @return the transformer to use for a save operation
899 |  |
          * @throws TransformerException if an error occurs
900 |  |
          * @since 1.3
901 |  |
          */
902 |  |
         protected Transformer createTransformer() throws TransformerException
903 |  |
         {
904 | 63 |
             Transformer transformer = TransformerFactory.newInstance()
905 |  |
                     .newTransformer();
906 |  |
907 | 62 |
             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
908 | 62 |
             if (getEncoding() != null)
909 |  |
             {
910 | 3 |
                 transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
911 |  |
             }
912 | 62 |
             if (getPublicID() != null)
913 |  |
             {
914 | 4 |
                 transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
915 |  |
                         getPublicID());
916 |  |
             }
917 | 62 |
             if (getSystemID() != null)
918 |  |
             {
919 | 4 |
                 transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
920 |  |
                         getSystemID());
921 |  |
             }
922 |  |
923 | 62 |
             return transformer;
924 |  |
         }
925 |  |
926 |  |
         /**
927 |  |
          * Creates a copy of this object. The new configuration object will contain
928 |  |
          * the same properties as the original, but it will lose any connection to a
929 |  |
          * source document (if one exists). This is to avoid race conditions if both
930 |  |
          * the original and the copy are modified and then saved.
931 |  |
          *
932 |  |
          * @return the copy
933 |  |
          */
934 |  |
         public Object clone()
935 |  |
         {
936 | 2 |
             XMLConfiguration copy = (XMLConfiguration) super.clone();
937 |  |
938 |  |
             // clear document related properties
939 | 2 |
             copy.document = null;
940 | 2 |
             copy.setDelegate(copy.createDelegate());
941 |  |
             // clear all references in the nodes, too
942 | 2 |
             clearReferences(copy.getRootNode());
943 |  |
944 | 2 |
             return copy;
945 |  |
         }
946 |  |
947 |  |
         /**
948 |  |
          * Creates the file configuration delegate for this object. This implementation
949 |  |
          * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
950 |  |
          * that deals with some specialities of <code>XMLConfiguration</code>.
951 |  |
          * @return the delegate for this object
952 |  |
          */
953 |  |
         protected FileConfigurationDelegate createDelegate()
954 |  |
         {
955 | 426 |
             return new XMLFileConfigurationDelegate();
956 |  |
         }
957 |  |
958 |  |
         /**
959 |  |
          * Adds a collection of nodes directly to this configuration. This
960 |  |
          * implementation ensures that the nodes to be added are of the correct node
961 |  |
          * type (they have to be converted to <code>XMLNode</code> if necessary).
962 |  |
          *
963 |  |
          * @param key the key where the nodes are to be added
964 |  |
          * @param nodes the collection with the new nodes
965 |  |
          * @since 1.5
966 |  |
          */
967 |  |
         public void addNodes(String key, Collection nodes)
968 |  |
         {
969 |  |
             Collection xmlNodes;
970 |  |
971 | 5 |
             if (nodes != null && !nodes.isEmpty())
972 |  |
             {
973 | 5 |
                 xmlNodes = new ArrayList(nodes.size());
974 | 5 |
                 for (Iterator it = nodes.iterator(); it.hasNext();)
975 |  |
                 {
976 | 6 |
                     xmlNodes.add(convertToXMLNode((ConfigurationNode) it.next()));
977 |  |
                 }
978 |  |
             }
979 |  |
             else
980 |  |
             {
981 | 0 |
                 xmlNodes = nodes;
982 |  |
             }
983 |  |
984 | 5 |
             super.addNodes(key, xmlNodes);
985 | 5 |
         }
986 |  |
987 |  |
         /**
988 |  |
          * Converts the specified node into a <code>XMLNode</code> if necessary.
989 |  |
          * This is required for nodes that are directly added, e.g. by
990 |  |
          * <code>addNodes()</code>. If the passed in node is already an instance
991 |  |
          * of <code>XMLNode</code>, it is directly returned, and conversion
992 |  |
          * stops. Otherwise a new <code>XMLNode</code> is created, and the
993 |  |
          * children are also converted.
994 |  |
          *
995 |  |
          * @param node the node to be converted
996 |  |
          * @return the converted node
997 |  |
          */
998 |  |
         private XMLNode convertToXMLNode(ConfigurationNode node)
999 |  |
         {
1000 | 8 |
             if (node instanceof XMLNode)
1001 |  |
             {
1002 | 2 |
                 return (XMLNode) node;
1003 |  |
             }
1004 |  |
1005 | 6 |
             XMLNode nd = (XMLNode) createNode(node.getName());
1006 | 6 |
             nd.setValue(node.getValue());
1007 | 6 |
             nd.setAttribute(node.isAttribute());
1008 | 6 |
             for (Iterator it = node.getChildren().iterator(); it.hasNext();)
1009 |  |
             {
1010 | 1 |
                 nd.addChild(convertToXMLNode((ConfigurationNode) it.next()));
1011 |  |
             }
1012 | 6 |
             for (Iterator it = node.getAttributes().iterator(); it.hasNext();)
1013 |  |
             {
1014 | 1 |
                 nd.addAttribute(convertToXMLNode((ConfigurationNode) it.next()));
1015 |  |
             }
1016 | 6 |
             return nd;
1017 |  |
         }
1018 |  |
1019 |  |
         /**
1020 |  |
          * <p>
1021 |  |
          * Registers the specified DTD URL for the specified public identifier.
1022 |  |
          * </p>
1023 |  |
          * <p>
1024 |  |
          * <code>XMLConfiguration</code> contains an internal
1025 |  |
          * <code>EntityResolver</code> implementation. This maps
1026 |  |
          * <code>PUBLICID</code>'s to URLs (from which the resource will be
1027 |  |
          * loaded). A common use case for this method is to register local URLs
1028 |  |
          * (possibly computed at runtime by a class loader) for DTDs. This allows
1029 |  |
          * the performance advantage of using a local version without having to
1030 |  |
          * ensure every <code>SYSTEM</code> URI on every processed XML document is
1031 |  |
          * local. This implementation provides only basic functionality. If more
1032 |  |
          * sophisticated features are required, using
1033 |  |
          * {@link #setDocumentBuilder(DocumentBuilder)} to set a custom
1034 |  |
          * <code>DocumentBuilder</code> (which also can be initialized with a
1035 |  |
          * custom <code>EntityResolver</code>) is recommended.
1036 |  |
          * </p>
1037 |  |
          * <p>
1038 |  |
          * <strong>Note:</strong> This method will have no effect when a custom
1039 |  |
          * <code>DocumentBuilder</code> has been set. (Setting a custom
1040 |  |
          * <code>DocumentBuilder</code> overrides the internal implementation.)
1041 |  |
          * </p>
1042 |  |
          * <p>
1043 |  |
          * <strong>Note:</strong> This method must be called before the
1044 |  |
          * configuration is loaded. So the default constructor of
1045 |  |
          * <code>XMLConfiguration</code> should be used, the location of the
1046 |  |
          * configuration file set, <code>registerEntityId()</code> called, and
1047 |  |
          * finally the <code>load()</code> method can be invoked.
1048 |  |
          * </p>
1049 |  |
          *
1050 |  |
          * @param publicId Public identifier of the DTD to be resolved
1051 |  |
          * @param entityURL The URL to use for reading this DTD
1052 |  |
          * @throws IllegalArgumentException if the public ID is undefined
1053 |  |
          * @since 1.5
1054 |  |
          */
1055 |  |
         public void registerEntityId(String publicId, URL entityURL)
1056 |  |
         {
1057 | 3 |
             if (publicId == null)
1058 |  |
             {
1059 | 1 |
                 throw new IllegalArgumentException("Public ID must not be null!");
1060 |  |
             }
1061 | 2 |
             getRegisteredEntities().put(publicId, entityURL);
1062 | 2 |
         }
1063 |  |
1064 |  |
         /**
1065 |  |
          * Resolves the requested external entity. This is the default
1066 |  |
          * implementation of the <code>EntityResolver</code> interface. It checks
1067 |  |
          * the passed in public ID against the registered entity IDs and uses a
1068 |  |
          * local URL if possible.
1069 |  |
          *
1070 |  |
          * @param publicId the public identifier of the entity being referenced
1071 |  |
          * @param systemId the system identifier of the entity being referenced
1072 |  |
          * @return an input source for the specified entity
1073 |  |
          * @throws SAXException if a parsing exception occurs
1074 |  |
          * @since 1.5
1075 |  |
          */
1076 |  |
         public InputSource resolveEntity(String publicId, String systemId)
1077 |  |
                 throws SAXException
1078 |  |
         {
1079 |  |
             // Has this system identifier been registered?
1080 | 7 |
             URL entityURL = null;
1081 | 7 |
             if (publicId != null)
1082 |  |
             {
1083 | 3 |
                 entityURL = (URL) getRegisteredEntities().get(publicId);
1084 |  |
             }
1085 |  |
1086 | 7 |
             if (entityURL != null)
1087 |  |
             {
1088 |  |
                 // Obtain an InputSource for this URL. This code is based on the
1089 |  |
                 // createInputSourceFromURL() method of Commons Digester.
1090 |  |
                 try
1091 |  |
                 {
1092 | 2 |
                     URLConnection connection = entityURL.openConnection();
1093 | 2 |
                     connection.setUseCaches(false);
1094 | 2 |
                     InputStream stream = connection.getInputStream();
1095 | 2 |
                     InputSource source = new InputSource(stream);
1096 | 2 |
                     source.setSystemId(entityURL.toExternalForm());
1097 | 2 |
                     return source;
1098 |  |
                 }
1099 | 0 |
                 catch (IOException e)
1100 |  |
                 {
1101 | 0 |
                     throw new SAXException(e);
1102 |  |
                 }
1103 |  |
             }
1104 |  |
             else
1105 |  |
             {
1106 |  |
                 // default processing behavior
1107 | 5 |
                 return null;
1108 |  |
             }
1109 |  |
         }
1110 |  |
1111 |  |
         /**
1112 |  |
          * Returns a map with the entity IDs that have been registered using the
1113 |  |
          * <code>registerEntityId()</code> method.
1114 |  |
          *
1115 |  |
          * @return a map with the registered entity IDs
1116 |  |
          */
1117 |  |
         Map getRegisteredEntities()
1118 |  |
         {
1119 | 85 |
             return registeredEntities;
1120 |  |
         }
1121 |  |
1122 |  |
         /**
1123 |  |
          * A specialized <code>Node</code> class that is connected with an XML
1124 |  |
          * element. Changes on a node are also performed on the associated element.
1125 |  |
          */
1126 |  |
         class XMLNode extends Node
1127 |  |
         {
1128 |  |
             /**
1129 |  |
              * The serial version UID.
1130 |  |
              */
1131 |  |
             private static final long serialVersionUID = -4133988932174596562L;
1132 |  |
1133 |  |
             /**
1134 |  |
              * Creates a new instance of <code>XMLNode</code> and initializes it
1135 |  |
              * with a name and the corresponding XML element.
1136 |  |
              *
1137 |  |
              * @param name the node's name
1138 |  |
              * @param elem the XML element
1139 |  |
              */
1140 |  |
             public XMLNode(String name, Element elem)
1141 | 34194 |
             {
1142 | 34194 |
                 super(name);
1143 | 34194 |
                 setReference(elem);
1144 | 34194 |
             }
1145 |  |
1146 |  |
             /**
1147 |  |
              * Sets the value of this node. If this node is associated with an XML
1148 |  |
              * element, this element will be updated, too.
1149 |  |
              *
1150 |  |
              * @param value the node's new value
1151 |  |
              */
1152 |  |
             public void setValue(Object value)
1153 |  |
             {
1154 | 82682 |
                 super.setValue(value);
1155 |  |
1156 | 82682 |
                 if (getReference() != null && document != null)
1157 |  |
                 {
1158 | 6118 |
                     if (isAttribute())
1159 |  |
                     {
1160 | 2111 |
                         updateAttribute();
1161 |  |
                     }
1162 |  |
                     else
1163 |  |
                     {
1164 | 4007 |
                         updateElement(value);
1165 |  |
                     }
1166 |  |
                 }
1167 | 82682 |
             }
1168 |  |
1169 |  |
             /**
1170 |  |
              * Updates the associated XML elements when a node is removed.
1171 |  |
              */
1172 |  |
             protected void removeReference()
1173 |  |
             {
1174 | 7361 |
                 if (getReference() != null)
1175 |  |
                 {
1176 | 6521 |
                     Element element = (Element) getReference();
1177 | 6521 |
                     if (isAttribute())
1178 |  |
                     {
1179 | 0 |
                         updateAttribute();
1180 |  |
                     }
1181 |  |
                     else
1182 |  |
                     {
1183 | 6521 |
                         org.w3c.dom.Node parentElem = element.getParentNode();
1184 | 6521 |
                         if (parentElem != null)
1185 |  |
                         {
1186 | 6521 |
                             parentElem.removeChild(element);
1187 |  |
                         }
1188 |  |
                     }
1189 |  |
                 }
1190 | 7361 |
             }
1191 |  |
1192 |  |
             /**
1193 |  |
              * Updates the node's value if it represents an element node.
1194 |  |
              *
1195 |  |
              * @param value the new value
1196 |  |
              */
1197 |  |
             private void updateElement(Object value)
1198 |  |
             {
1199 | 4007 |
                 Text txtNode = findTextNodeForUpdate();
1200 | 4007 |
                 if (value == null)
1201 |  |
                 {
1202 |  |
                     // remove text
1203 | 3982 |
                     if (txtNode != null)
1204 |  |
                     {
1205 | 3482 |
                         ((Element) getReference()).removeChild(txtNode);
1206 |  |
                     }
1207 |  |
                 }
1208 |  |
                 else
1209 |  |
                 {
1210 | 25 |
                     if (txtNode == null)
1211 |  |
                     {
1212 | 4 |
                         txtNode = document
1213 |  |
                                 .createTextNode(PropertyConverter.escapeDelimiters(
1214 |  |
                                         value.toString(), getListDelimiter()));
1215 | 4 |
                         if (((Element) getReference()).getFirstChild() != null)
1216 |  |
                         {
1217 | 1 |
                             ((Element) getReference()).insertBefore(txtNode,
1218 |  |
                                     ((Element) getReference()).getFirstChild());
1219 |  |
                         }
1220 |  |
                         else
1221 |  |
                         {
1222 | 3 |
                             ((Element) getReference()).appendChild(txtNode);
1223 |  |
                         }
1224 |  |
                     }
1225 |  |
                     else
1226 |  |
                     {
1227 | 21 |
                         txtNode.setNodeValue(PropertyConverter.escapeDelimiters(
1228 |  |
                                 value.toString(), getListDelimiter()));
1229 |  |
                     }
1230 |  |
                 }
1231 | 4007 |
             }
1232 |  |
1233 |  |
             /**
1234 |  |
              * Updates the node's value if it represents an attribute.
1235 |  |
              *
1236 |  |
              */
1237 |  |
             private void updateAttribute()
1238 |  |
             {
1239 | 2111 |
                 XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter());
1240 | 2111 |
             }
1241 |  |
1242 |  |
             /**
1243 |  |
              * Returns the only text node of this element for update. This method is
1244 |  |
              * called when the element's text changes. Then all text nodes except
1245 |  |
              * for the first are removed. A reference to the first is returned or
1246 |  |
              * <b>null </b> if there is no text node at all.
1247 |  |
              *
1248 |  |
              * @return the first and only text node
1249 |  |
              */
1250 |  |
             private Text findTextNodeForUpdate()
1251 |  |
             {
1252 | 4007 |
                 Text result = null;
1253 | 4007 |
                 Element elem = (Element) getReference();
1254 |  |
                 // Find all Text nodes
1255 | 4007 |
                 NodeList children = elem.getChildNodes();
1256 | 4007 |
                 Collection textNodes = new ArrayList();
1257 | 9389 |
                 for (int i = 0; i < children.getLength(); i++)
1258 |  |
                 {
1259 | 5382 |
                     org.w3c.dom.Node nd = children.item(i);
1260 | 5382 |
                     if (nd instanceof Text)
1261 |  |
                     {
1262 | 4548 |
                         if (result == null)
1263 |  |
                         {
1264 | 3708 |
                             result = (Text) nd;
1265 |  |
                         }
1266 |  |
                         else
1267 |  |
                         {
1268 | 840 |
                             textNodes.add(nd);
1269 |  |
                         }
1270 |  |
                     }
1271 |  |
                 }
1272 |  |
1273 |  |
                 // We don't want CDATAs
1274 | 4007 |
                 if (result instanceof CDATASection)
1275 |  |
                 {
1276 | 205 |
                     textNodes.add(result);
1277 | 205 |
                     result = null;
1278 |  |
                 }
1279 |  |
1280 |  |
                 // Remove all but the first Text node
1281 | 4007 |
                 for (Iterator it = textNodes.iterator(); it.hasNext();)
1282 |  |
                 {
1283 | 1045 |
                     elem.removeChild((org.w3c.dom.Node) it.next());
1284 |  |
                 }
1285 | 4007 |
                 return result;
1286 |  |
             }
1287 |  |
         }
1288 |  |
1289 |  |
         /**
1290 |  |
          * A concrete <code>BuilderVisitor</code> that can construct XML
1291 |  |
          * documents.
1292 |  |
          */
1293 |  |
         static class XMLBuilderVisitor extends BuilderVisitor
1294 |  |
         {
1295 |  |
             /** Stores the document to be constructed. */
1296 |  |
             private Document document;
1297 |  |
1298 |  |
             /** Stores the list delimiter.*/
1299 | 62 |
             private char listDelimiter = AbstractConfiguration.
1300 |  |
                     getDefaultListDelimiter();
1301 |  |
1302 |  |
             /**
1303 |  |
              * Creates a new instance of <code>XMLBuilderVisitor</code>
1304 |  |
              *
1305 |  |
              * @param doc the document to be created
1306 |  |
              * @param listDelimiter the delimiter for attribute properties with multiple values
1307 |  |
              */
1308 |  |
             public XMLBuilderVisitor(Document doc, char listDelimiter)
1309 | 62 |
             {
1310 | 62 |
                 document = doc;
1311 | 62 |
                 this.listDelimiter = listDelimiter;
1312 | 62 |
             }
1313 |  |
1314 |  |
             /**
1315 |  |
              * Processes the node hierarchy and adds new nodes to the document.
1316 |  |
              *
1317 |  |
              * @param rootNode the root node
1318 |  |
              */
1319 |  |
             public void processDocument(Node rootNode)
1320 |  |
             {
1321 | 62 |
                 rootNode.visit(this, null);
1322 | 62 |
             }
1323 |  |
1324 |  |
             /**
1325 |  |
              * Inserts a new node. This implementation ensures that the correct
1326 |  |
              * XML element is created and inserted between the given siblings.
1327 |  |
              *
1328 |  |
              * @param newNode the node to insert
1329 |  |
              * @param parent the parent node
1330 |  |
              * @param sibling1 the first sibling
1331 |  |
              * @param sibling2 the second sibling
1332 |  |
              * @return the new node
1333 |  |
              */
1334 |  |
             protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
1335 |  |
             {
1336 | 670 |
                 if (newNode.isAttribute())
1337 |  |
                 {
1338 | 124 |
                     updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter);
1339 | 124 |
                     return null;
1340 |  |
                 }
1341 |  |
1342 |  |
                 else
1343 |  |
                 {
1344 | 546 |
                     Element elem = document.createElement(newNode.getName());
1345 | 546 |
                     if (newNode.getValue() != null)
1346 |  |
                     {
1347 | 396 |
                         String txt = newNode.getValue().toString();
1348 | 396 |
                         if (listDelimiter != 0)
1349 |  |
                         {
1350 | 394 |
                             txt = PropertyConverter.escapeDelimiters(txt, listDelimiter);
1351 |  |
                         }
1352 | 396 |
                         elem.appendChild(document.createTextNode(txt));
1353 |  |
                     }
1354 | 546 |
                     if (sibling2 == null)
1355 |  |
                     {
1356 | 477 |
                         getElement(parent).appendChild(elem);
1357 |  |
                     }
1358 | 69 |
                     else if (sibling1 != null)
1359 |  |
                     {
1360 | 46 |
                         getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
1361 |  |
                     }
1362 |  |
                     else
1363 |  |
                     {
1364 | 23 |
                         getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
1365 |  |
                     }
1366 | 546 |
                     return elem;
1367 |  |
                 }
1368 |  |
             }
1369 |  |
1370 |  |
             /**
1371 |  |
              * Helper method for updating the value of the specified node's
1372 |  |
              * attribute with the given name.
1373 |  |
              *
1374 |  |
              * @param node the affected node
1375 |  |
              * @param elem the element that is associated with this node
1376 |  |
              * @param name the name of the affected attribute
1377 |  |
              * @param listDelimiter the delimiter for attributes with multiple values
1378 |  |
              */
1379 |  |
             private static void updateAttribute(Node node, Element elem, String name, char listDelimiter)
1380 |  |
             {
1381 | 2235 |
                 if (node != null && elem != null)
1382 |  |
                 {
1383 | 2235 |
                     List attrs = node.getAttributes(name);
1384 | 2235 |
                     StringBuffer buf = new StringBuffer();
1385 | 2235 |
                     char delimiter = (listDelimiter != 0) ? listDelimiter : ATTR_VALUE_DELIMITER;
1386 | 2235 |
                     for (Iterator it = attrs.iterator(); it.hasNext();)
1387 |  |
                     {
1388 | 3169 |
                         Node attr = (Node) it.next();
1389 | 3169 |
                         if (attr.getValue() != null)
1390 |  |
                         {
1391 | 626 |
                             if (buf.length() > 0)
1392 |  |
                             {
1393 | 173 |
                                 buf.append(delimiter);
1394 |  |
                             }
1395 | 626 |
                             buf.append(PropertyConverter.escapeDelimiters(attr
1396 |  |
                                     .getValue().toString(), delimiter));
1397 |  |
                         }
1398 | 3169 |
                         attr.setReference(elem);
1399 |  |
                     }
1400 |  |
1401 | 2235 |
                     if (buf.length() < 1)
1402 |  |
                     {
1403 | 1782 |
                         elem.removeAttribute(name);
1404 |  |
                     }
1405 |  |
                     else
1406 |  |
                     {
1407 | 453 |
                         elem.setAttribute(name, buf.toString());
1408 |  |
                     }
1409 |  |
                 }
1410 | 2235 |
             }
1411 |  |
1412 |  |
             /**
1413 |  |
              * Updates the value of the specified attribute of the given node.
1414 |  |
              * Because there can be multiple child nodes representing this attribute
1415 |  |
              * the new value is determined by iterating over all those child nodes.
1416 |  |
              *
1417 |  |
              * @param node the affected node
1418 |  |
              * @param name the name of the attribute
1419 |  |
              * @param listDelimiter the delimiter for attributes with multiple values
1420 |  |
              */
1421 |  |
             static void updateAttribute(Node node, String name, char listDelimiter)
1422 |  |
             {
1423 | 2111 |
                 if (node != null)
1424 |  |
                 {
1425 | 2111 |
                     updateAttribute(node, (Element) node.getReference(), name, listDelimiter);
1426 |  |
                 }
1427 | 2111 |
             }
1428 |  |
1429 |  |
             /**
1430 |  |
              * Helper method for accessing the element of the specified node.
1431 |  |
              *
1432 |  |
              * @param node the node
1433 |  |
              * @return the element of this node
1434 |  |
              */
1435 |  |
             private Element getElement(Node node)
1436 |  |
             {
1437 |  |
                 // special treatment for root node of the hierarchy
1438 | 739 |
                 return (node.getName() != null && node.getReference() != null) ? (Element) node
1439 |  |
                         .getReference()
1440 |  |
                         : document.getDocumentElement();
1441 |  |
             }
1442 |  |
         }
1443 |  |
1444 |  |
         /**
1445 |  |
          * A special implementation of the <code>FileConfiguration</code> interface that is
1446 |  |
          * used internally to implement the <code>FileConfiguration</code> methods
1447 |  |
          * for <code>XMLConfiguration</code>, too.
1448 |  |
          */
1449 | 852 |
         private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
1450 |  |
         {
1451 |  |
             public void load(InputStream in) throws ConfigurationException
1452 |  |
             {
1453 | 454 |
                 XMLConfiguration.this.load(in);
1454 | 451 |
             }
1455 |  |
         }
1456 |  |
     }