~ubuntu-branches/ubuntu/saucy/commons-configuration/saucy

« back to all changes in this revision

Viewing changes to src/main/java/org/apache/commons/configuration/XMLConfiguration.java

  • Committer: Package Import Robot
  • Author(s): Emmanuel Bourg
  • Date: 2013-07-01 16:29:44 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20130701162944-98waq5gogha5gpn1
Tags: 1.9-1
* New upstream release
* debian/control:
  - Updated Standards-Version to 3.9.4 (no changes)
  - Use canonical URLs for the Vcs-* fields
  - Added new build dependencies (libjavacc-maven-plugin-java, junit4)
  - Upgraded the dependency on the Servlet API (2.5 -> 3.0)
  - Removed the dependency on the Activation Framework (glassfish-activation)
  - Replaced the dependency on glassfish-mail with libgnumail-java
  - Removed the unused dependencies:
    liblog4j1.2-java-doc, libmaven-assembly-plugin-java
  - Replaced the dependency on libcommons-jexl-java by libcommons-jexl2-java
* debian/watch: Changed to point the official Apache distribution server
* Removed the obsolete file debian/ant.properties
* Installed the upstream changelog in the binary packages
* Added the report plugins to maven.ignoreRules
* Added the classpath attribute to the jar manifest

Show diffs side-by-side

added added

removed removed

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