View Javadoc
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.configuration2;
19  
20  import static org.apache.commons.configuration2.TempDirUtils.newFile;
21  import static org.hamcrest.CoreMatchers.containsString;
22  import static org.hamcrest.MatcherAssert.assertThat;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertNull;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  import java.io.ByteArrayInputStream;
32  import java.io.File;
33  import java.io.FileOutputStream;
34  import java.io.IOException;
35  import java.io.StringReader;
36  import java.io.StringWriter;
37  import java.nio.charset.StandardCharsets;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Collection;
41  import java.util.List;
42  
43  import javax.xml.parsers.DocumentBuilder;
44  import javax.xml.parsers.DocumentBuilderFactory;
45  import javax.xml.parsers.ParserConfigurationException;
46  import javax.xml.transform.Transformer;
47  import javax.xml.transform.TransformerFactoryConfigurationError;
48  
49  import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
50  import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
51  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
52  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
53  import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
54  import org.apache.commons.configuration2.ex.ConfigurationException;
55  import org.apache.commons.configuration2.io.FileHandler;
56  import org.apache.commons.configuration2.resolver.CatalogResolver;
57  import org.apache.commons.configuration2.tree.ImmutableNode;
58  import org.apache.commons.configuration2.tree.NodeStructureHelper;
59  import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
60  import org.apache.commons.lang3.StringUtils;
61  import org.junit.jupiter.api.BeforeEach;
62  import org.junit.jupiter.api.Test;
63  import org.junit.jupiter.api.io.TempDir;
64  import org.w3c.dom.Document;
65  import org.xml.sax.SAXException;
66  import org.xml.sax.SAXParseException;
67  import org.xml.sax.helpers.DefaultHandler;
68  
69  /**
70   * test for loading and saving xml properties files
71   */
72  public class TestXMLConfiguration {
73      /**
74       * A thread used for testing concurrent access to a builder.
75       */
76      private static final class ReloadThread extends Thread {
77          private final FileBasedConfigurationBuilder<?> builder;
78  
79          ReloadThread(final FileBasedConfigurationBuilder<?> confBulder) {
80              builder = confBulder;
81          }
82  
83          @Override
84          public void run() {
85              for (int i = 0; i < LOOP_COUNT; i++) {
86                  builder.resetResult();
87              }
88          }
89      }
90  
91      /** XML Catalog */
92      private static final String CATALOG_FILES = ConfigurationAssert.getTestFile("catalog.xml").getAbsolutePath();
93  
94      /** Constant for the used encoding. */
95      static final String ENCODING = StandardCharsets.ISO_8859_1.name();
96  
97      /** Constant for the test system ID. */
98      static final String SYSTEM_ID = "properties.dtd";
99  
100     /** Constant for the test public ID. */
101     static final String PUBLIC_ID = "-//Commons Configuration//DTD Test Configuration 1.3//EN";
102 
103     /** Constant for the DOCTYPE declaration. */
104     static final String DOCTYPE_DECL = " PUBLIC \"" + PUBLIC_ID + "\" \"" + SYSTEM_ID + "\">";
105 
106     /** Constant for the DOCTYPE prefix. */
107     static final String DOCTYPE = "<!DOCTYPE ";
108 
109     /** Constant for the transformer factory property. */
110     static final String PROP_FACTORY = "javax.xml.transform.TransformerFactory";
111 
112     /** Constant for the number of test threads. */
113     private static final int THREAD_COUNT = 5;
114     /** Constant for the number of loops in tests with multiple threads. */
115     private static final int LOOP_COUNT = 100;
116 
117     /**
118      * Creates a new XMLConfiguration and loads the specified file.
119      *
120      * @param fileName the name of the file to be loaded
121      * @return the newly created configuration instance
122      * @throws ConfigurationException if an error occurs
123      */
124     private static XMLConfiguration createFromFile(final String fileName) throws ConfigurationException {
125         final XMLConfiguration config = new XMLConfiguration();
126         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
127         load(config, fileName);
128         return config;
129     }
130 
131     /**
132      * Helper method for loading the specified configuration file.
133      *
134      * @param config the configuration
135      * @param fileName the name of the file to be loaded
136      * @throws ConfigurationException if an error occurs
137      */
138     private static void load(final XMLConfiguration config, final String fileName) throws ConfigurationException {
139         final FileHandler handler = new FileHandler(config);
140         handler.setFileName(fileName);
141         handler.load();
142     }
143 
144     /** A folder for temporary files. */
145     @TempDir
146     public File tempFolder;
147 
148     /** The File that we test with */
149     private final String testProperties = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
150 
151     private final String testProperties2 = ConfigurationAssert.getTestFile("testDigesterConfigurationInclude1.xml").getAbsolutePath();
152 
153     private File testSaveConf;
154 
155     private File testSaveFile;
156 
157     private final String testFile2 = ConfigurationAssert.getTestFile("sample.xml").getAbsolutePath();
158 
159     private XMLConfiguration conf;
160 
161     /**
162      * Helper method for testing whether a configuration was correctly saved to the default output file.
163      *
164      * @return the newly loaded configuration
165      * @throws ConfigurationException if an error occurs
166      */
167     private XMLConfiguration checkSavedConfig() throws ConfigurationException {
168         return checkSavedConfig(testSaveConf);
169     }
170 
171     /**
172      * Tests whether the saved configuration file matches the original data.
173      *
174      * @param saveFile the saved configuration file
175      * @return the newly loaded configuration
176      * @throws ConfigurationException if an error occurs
177      */
178     private XMLConfiguration checkSavedConfig(final File saveFile) throws ConfigurationException {
179         final XMLConfiguration config = createFromFile(saveFile.getAbsolutePath());
180         ConfigurationAssert.assertConfigurationEquals(conf, config);
181         return config;
182     }
183 
184     /**
185      * Helper method for testing saving and loading a configuration when delimiter parsing is disabled.
186      *
187      * @param key the key to be checked
188      * @throws ConfigurationException if an error occurs
189      */
190     private void checkSaveDelimiterParsingDisabled(final String key) throws ConfigurationException {
191         conf.clear();
192         conf.setListDelimiterHandler(new DisabledListDelimiterHandler());
193         load(conf, testProperties);
194         conf.setProperty(key, "C:\\Temp\\,C:\\Data\\");
195         conf.addProperty(key, "a,b,c");
196         saveTestConfig();
197         final XMLConfiguration checkConf = new XMLConfiguration();
198         checkConf.setListDelimiterHandler(conf.getListDelimiterHandler());
199         load(checkConf, testSaveConf.getAbsolutePath());
200         ConfigurationAssert.assertConfigurationEquals(conf, checkConf);
201     }
202 
203     /**
204      * Creates a validating document builder.
205      *
206      * @return the document builder
207      * @throws ParserConfigurationException if an error occurs
208      */
209     private DocumentBuilder createValidatingDocBuilder() throws ParserConfigurationException {
210         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
211         factory.setValidating(true);
212         final DocumentBuilder builder = factory.newDocumentBuilder();
213         builder.setErrorHandler(new DefaultHandler() {
214             @Override
215             public void error(final SAXParseException ex) throws SAXException {
216                 throw ex;
217             }
218         });
219         return builder;
220     }
221 
222     private Document parseXml(final String xml) throws SAXException, IOException, ParserConfigurationException {
223         return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
224     }
225 
226     /**
227      * Removes the test output file if it exists.
228      */
229     private void removeTestFile() {
230         if (testSaveConf.exists()) {
231             assertTrue(testSaveConf.delete());
232         }
233     }
234 
235     /**
236      * Helper method for saving the test configuration to the default output file.
237      *
238      * @throws ConfigurationException if an error occurs
239      */
240     private void saveTestConfig() throws ConfigurationException {
241         final FileHandler handler = new FileHandler(conf);
242         handler.save(testSaveConf);
243     }
244 
245     @BeforeEach
246     public void setUp() throws Exception {
247         testSaveConf = newFile("testsave.xml", tempFolder);
248         testSaveFile = newFile("testsample2.xml", tempFolder);
249         conf = createFromFile(testProperties);
250         removeTestFile();
251     }
252 
253     @Test
254     public void testAddList() {
255         conf.addProperty("test.array", "value1");
256         conf.addProperty("test.array", "value2");
257 
258         final List<Object> list = conf.getList("test.array");
259         assertEquals(Arrays.asList("value1", "value2"), list);
260     }
261 
262     /**
263      * Tests saving a configuration after a node was added. Test for CONFIGURATION-294.
264      */
265     @Test
266     public void testAddNodesAndSave() throws ConfigurationException {
267         final ImmutableNode.Builder bldrNode = new ImmutableNode.Builder(1);
268         bldrNode.addChild(NodeStructureHelper.createNode("child", null));
269         bldrNode.addAttribute("attr", "");
270         final ImmutableNode node2 = NodeStructureHelper.createNode("test2", null);
271         conf.addNodes("add.nodes", Arrays.asList(bldrNode.name("test").create(), node2));
272         saveTestConfig();
273         conf.setProperty("add.nodes.test", "true");
274         conf.setProperty("add.nodes.test.child", "yes");
275         conf.setProperty("add.nodes.test[@attr]", "existing");
276         conf.setProperty("add.nodes.test2", "anotherValue");
277         saveTestConfig();
278         final XMLConfiguration c2 = new XMLConfiguration();
279         load(c2, testSaveConf.getAbsolutePath());
280         assertEquals("true", c2.getString("add.nodes.test"));
281         assertEquals("yes", c2.getString("add.nodes.test.child"));
282         assertEquals("existing", c2.getString("add.nodes.test[@attr]"));
283         assertEquals("anotherValue", c2.getString("add.nodes.test2"));
284     }
285 
286     /**
287      * Tests adding nodes from another configuration.
288      */
289     @Test
290     public void testAddNodesCopy() throws ConfigurationException {
291         final XMLConfiguration c2 = new XMLConfiguration();
292         load(c2, testProperties2);
293         conf.addNodes("copiedProperties", c2.getModel().getNodeHandler().getRootNode().getChildren());
294         saveTestConfig();
295         checkSavedConfig();
296     }
297 
298     /**
299      * Tests whether it is possible to add nodes to a XMLConfiguration through a SubnodeConfiguration and whether these
300      * nodes have the correct type. This test is related to CONFIGURATION-472.
301      */
302     @Test
303     public void testAddNodesToSubnodeConfiguration() throws Exception {
304         final HierarchicalConfiguration<ImmutableNode> sub = conf.configurationAt("element2", true);
305         sub.addProperty("newKey", "newvalue");
306         assertEquals("newvalue", conf.getString("element2.newKey"));
307     }
308 
309     @Test
310     public void testAddObjectAttribute() {
311         conf.addProperty("test.boolean[@value]", Boolean.TRUE);
312         assertTrue(conf.getBoolean("test.boolean[@value]"));
313     }
314 
315     @Test
316     public void testAddObjectProperty() {
317         // add a non string property
318         conf.addProperty("test.boolean", Boolean.TRUE);
319         assertTrue(conf.getBoolean("test.boolean"));
320     }
321 
322     @Test
323     public void testAddProperty() {
324         // add a property to a non initialized xml configuration
325         final XMLConfiguration config = new XMLConfiguration();
326         config.addProperty("test.string", "hello");
327 
328         assertEquals("hello", config.getString("test.string"));
329     }
330 
331     /**
332      * Tests whether list properties are added correctly if delimiter parsing is disabled. This test is related to
333      * CONFIGURATION-495.
334      */
335     @Test
336     public void testAddPropertyListWithDelimiterParsingDisabled() throws ConfigurationException {
337         conf.clear();
338         final String prop = "delimiterListProp";
339         conf.setListDelimiterHandler(DisabledListDelimiterHandler.INSTANCE);
340         final List<String> list = Arrays.asList("val", "val2", "val3");
341         conf.addProperty(prop, list);
342         saveTestConfig();
343         final XMLConfiguration conf2 = new XMLConfiguration();
344         load(conf2, testSaveConf.getAbsolutePath());
345         assertEquals(list, conf2.getProperty(prop));
346     }
347 
348     /**
349      * Tests if a second file can be appended to a first.
350      */
351     @Test
352     public void testAppend() throws Exception {
353         load(conf, testProperties2);
354         assertEquals("value", conf.getString("element"));
355         assertEquals("tasks", conf.getString("table.name"));
356 
357         saveTestConfig();
358         conf = createFromFile(testSaveConf.getAbsolutePath());
359         assertEquals("value", conf.getString("element"));
360         assertEquals("tasks", conf.getString("table.name"));
361         assertEquals("application", conf.getString("table[@tableType]"));
362     }
363 
364     /**
365      * Tries to create an attribute with multiple values. Only the first value is taken into account.
366      */
367     @Test
368     public void testAttributeKeyWithMultipleValues() throws ConfigurationException {
369         conf.addProperty("errorTest[@multiAttr]", Arrays.asList("v1", "v2"));
370         saveTestConfig();
371         final XMLConfiguration checkConfig = new XMLConfiguration();
372         load(checkConfig, testSaveConf.getAbsolutePath());
373         assertEquals("v1", checkConfig.getString("errorTest[@multiAttr]"));
374     }
375 
376     /**
377      * Tests whether the addNodes() method triggers an auto save.
378      */
379     @Test
380     public void testAutoSaveAddNodes() throws ConfigurationException {
381         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
382         builder.configure(new FileBasedBuilderParametersImpl().setFileName(testProperties));
383         conf = builder.getConfiguration();
384         builder.getFileHandler().setFile(testSaveConf);
385         builder.setAutoSave(true);
386         final ImmutableNode node = NodeStructureHelper.createNode("addNodesTest", Boolean.TRUE);
387         final Collection<ImmutableNode> nodes = new ArrayList<>(1);
388         nodes.add(node);
389         conf.addNodes("test.autosave", nodes);
390         final XMLConfiguration c2 = new XMLConfiguration();
391         load(c2, testSaveConf.getAbsolutePath());
392         assertTrue(c2.getBoolean("test.autosave.addNodesTest"));
393     }
394 
395     /**
396      * Tests whether the auto save mechanism is triggered by changes at a subnode configuration.
397      */
398     @Test
399     public void testAutoSaveWithSubnodeConfig() throws ConfigurationException {
400         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
401         builder.configure(new FileBasedBuilderParametersImpl().setFileName(testProperties));
402         conf = builder.getConfiguration();
403         builder.getFileHandler().setFile(testSaveConf);
404         builder.setAutoSave(true);
405         final String newValue = "I am autosaved";
406         final Configuration sub = conf.configurationAt("element2.subelement", true);
407         sub.setProperty("subsubelement", newValue);
408         assertEquals(newValue, conf.getString("element2.subelement.subsubelement"));
409         final XMLConfiguration conf2 = new XMLConfiguration();
410         load(conf2, testSaveConf.getAbsolutePath());
411         assertEquals(newValue, conf2.getString("element2.subelement.subsubelement"));
412     }
413 
414     /**
415      * Tests whether a subnode configuration created from another subnode configuration of a XMLConfiguration can trigger
416      * the auto save mechanism.
417      */
418     @Test
419     public void testAutoSaveWithSubSubnodeConfig() throws ConfigurationException {
420         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
421         builder.configure(new FileBasedBuilderParametersImpl().setFileName(testProperties));
422         conf = builder.getConfiguration();
423         builder.getFileHandler().setFile(testSaveConf);
424         builder.setAutoSave(true);
425         final String newValue = "I am autosaved";
426         final HierarchicalConfiguration<?> sub1 = conf.configurationAt("element2", true);
427         final HierarchicalConfiguration<?> sub2 = sub1.configurationAt("subelement", true);
428         sub2.setProperty("subsubelement", newValue);
429         assertEquals(newValue, conf.getString("element2.subelement.subsubelement"));
430         final XMLConfiguration conf2 = new XMLConfiguration();
431         load(conf2, testSaveConf.getAbsolutePath());
432         assertEquals(newValue, conf2.getString("element2.subelement.subsubelement"));
433     }
434 
435     @Test
436     public void testClearAttributeMultipleDisjoined() throws Exception {
437         String key = "clear.list.item[@id]";
438         conf.clearProperty(key);
439         assertNull(conf.getProperty(key));
440         assertNull(conf.getProperty(key));
441         key = "clear.list.item";
442         assertNotNull(conf.getProperty(key));
443         assertNotNull(conf.getProperty(key));
444     }
445 
446     @Test
447     public void testClearAttributeNonExisting() {
448         final String key = "clear[@id]";
449         conf.clearProperty(key);
450         assertNull(conf.getProperty(key));
451         assertNull(conf.getProperty(key));
452     }
453 
454     @Test
455     public void testClearAttributeSingle() {
456         String key = "clear.element2[@id]";
457         conf.clearProperty(key);
458         assertNull(conf.getProperty(key));
459         assertNull(conf.getProperty(key));
460         key = "clear.element2";
461         assertNotNull(conf.getProperty(key));
462         assertNotNull(conf.getProperty(key));
463     }
464 
465     @Test
466     public void testClearPropertyCData() {
467         final String key = "clear.cdata";
468         conf.clearProperty(key);
469         assertNull(conf.getProperty(key));
470         assertNull(conf.getProperty(key));
471     }
472 
473     @Test
474     public void testClearPropertyMultipleDisjoined() throws Exception {
475         final String key = "list.item";
476         conf.clearProperty(key);
477         assertNull(conf.getProperty(key));
478         assertNull(conf.getProperty(key));
479     }
480 
481     @Test
482     public void testClearPropertyMultipleSiblings() {
483         String key = "clear.list.item";
484         conf.clearProperty(key);
485         assertNull(conf.getProperty(key));
486         assertNull(conf.getProperty(key));
487         key = "clear.list.item[@id]";
488         assertNotNull(conf.getProperty(key));
489         assertNotNull(conf.getProperty(key));
490     }
491 
492     @Test
493     public void testClearPropertyNonText() {
494         final String key = "clear.comment";
495         conf.clearProperty(key);
496         assertNull(conf.getProperty(key));
497         assertNull(conf.getProperty(key));
498     }
499 
500     @Test
501     public void testClearPropertyNotExisting() {
502         final String key = "clearly";
503         conf.clearProperty(key);
504         assertNull(conf.getProperty(key));
505         assertNull(conf.getProperty(key));
506     }
507 
508     @Test
509     public void testClearPropertySingleElement() {
510         final String key = "clear.element";
511         conf.clearProperty(key);
512         assertNull(conf.getProperty(key));
513         assertNull(conf.getProperty(key));
514     }
515 
516     @Test
517     public void testClearPropertySingleElementWithAttribute() {
518         String key = "clear.element2";
519         conf.clearProperty(key);
520         assertNull(conf.getProperty(key));
521         assertNull(conf.getProperty(key));
522         key = "clear.element2[@id]";
523         assertNotNull(conf.getProperty(key));
524         assertNotNull(conf.getProperty(key));
525     }
526 
527     /**
528      * Tests removing the text of the root element.
529      */
530     @Test
531     public void testClearTextRootElement() throws ConfigurationException {
532         final String xml = "<e a=\"v\">text</e>";
533         conf.clear();
534         final StringReader in = new StringReader(xml);
535         final FileHandler handler = new FileHandler(conf);
536         handler.load(in);
537         assertEquals("text", conf.getString(""));
538 
539         conf.clearProperty("");
540         saveTestConfig();
541         checkSavedConfig();
542     }
543 
544     /**
545      * Tests the clone() method.
546      */
547     @Test
548     public void testClone() {
549         final Configuration c = (Configuration) conf.clone();
550         final XMLConfiguration copy = assertInstanceOf(XMLConfiguration.class, c);
551         assertNotNull(conf.getDocument());
552         assertNull(copy.getDocument());
553 
554         copy.setProperty("element3", "clonedValue");
555         assertEquals("value", conf.getString("element3"));
556         conf.setProperty("element3[@name]", "originalFoo");
557         assertEquals("foo", copy.getString("element3[@name]"));
558     }
559 
560     /**
561      * Tests saving a configuration after cloning to ensure that the clone and the original are completely detached.
562      */
563     @Test
564     public void testCloneWithSave() throws ConfigurationException {
565         final XMLConfiguration c = (XMLConfiguration) conf.clone();
566         c.addProperty("test.newProperty", Boolean.TRUE);
567         conf.addProperty("test.orgProperty", Boolean.TRUE);
568         new FileHandler(c).save(testSaveConf);
569         final XMLConfiguration c2 = new XMLConfiguration();
570         load(c2, testSaveConf.getAbsolutePath());
571         assertTrue(c2.getBoolean("test.newProperty"));
572         assertFalse(c2.containsKey("test.orgProperty"));
573     }
574 
575     /**
576      * Tests access to tag names with delimiter characters.
577      */
578     @Test
579     public void testComplexNames() {
580         assertEquals("Name with dot", conf.getString("complexNames.my..elem"));
581         assertEquals("Another dot", conf.getString("complexNames.my..elem.sub..elem"));
582     }
583 
584     @Test
585     public void testConcurrentGetAndReload() throws ConfigurationException, InterruptedException {
586         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
587         builder.configure(new FileBasedBuilderParametersImpl().setFileName(testProperties));
588         XMLConfiguration config = builder.getConfiguration();
589         assertNotNull(config.getProperty("test.short"));
590 
591         final Thread[] testThreads = new Thread[THREAD_COUNT];
592         for (int i = 0; i < testThreads.length; ++i) {
593             testThreads[i] = new ReloadThread(builder);
594             testThreads[i].start();
595         }
596 
597         for (int i = 0; i < LOOP_COUNT; i++) {
598             config = builder.getConfiguration();
599             assertNotNull(config.getProperty("test.short"));
600         }
601 
602         for (final Thread testThread : testThreads) {
603             testThread.join();
604         }
605     }
606 
607     /**
608      * Tests the copy constructor for null input.
609      */
610     @Test
611     public void testCopyNull() {
612         conf = new XMLConfiguration(null);
613         assertTrue(conf.isEmpty());
614         assertEquals("configuration", conf.getRootElementName());
615     }
616 
617     /**
618      * Tests whether the name of the root element is copied when a configuration is created using the copy constructor.
619      */
620     @Test
621     public void testCopyRootName() throws ConfigurationException {
622         final String rootName = "rootElement";
623         final String xml = "<" + rootName + "><test>true</test></" + rootName + ">";
624         conf.clear();
625         new FileHandler(conf).load(new StringReader(xml));
626         XMLConfiguration copy = new XMLConfiguration(conf);
627         assertEquals(rootName, copy.getRootElementName());
628         new FileHandler(copy).save(testSaveConf);
629         copy = new XMLConfiguration();
630         load(copy, testSaveConf.getAbsolutePath());
631         assertEquals(rootName, copy.getRootElementName());
632     }
633 
634     /**
635      * Tests whether the name of the root element is copied for a configuration for which not yet a document exists.
636      */
637     @Test
638     public void testCopyRootNameNoDocument() throws ConfigurationException {
639         final String rootName = "rootElement";
640         conf = new XMLConfiguration();
641         conf.setRootElementName(rootName);
642         conf.setProperty("test", Boolean.TRUE);
643         final XMLConfiguration copy = new XMLConfiguration(conf);
644         assertEquals(rootName, copy.getRootElementName());
645         new FileHandler(copy).save(testSaveConf);
646         load(copy, testSaveConf.getAbsolutePath());
647         assertEquals(rootName, copy.getRootElementName());
648     }
649 
650     /**
651      * Tests setting a custom document builder.
652      */
653     @Test
654     public void testCustomDocBuilder() throws Exception {
655         // Load an invalid XML file with the default (non validating)
656         // doc builder. This should work...
657         conf = new XMLConfiguration();
658         load(conf, ConfigurationAssert.getTestFile("testValidateInvalid.xml").getAbsolutePath());
659         assertEquals("customers", conf.getString("table.name"));
660         assertFalse(conf.containsKey("table.fields.field(1).type"));
661     }
662 
663     /**
664      * Tests whether a validating document builder detects a validation error.
665      */
666     @Test
667     public void testCustomDocBuilderValidationError() throws Exception {
668         final DocumentBuilder builder = createValidatingDocBuilder();
669         conf = new XMLConfiguration();
670         conf.setDocumentBuilder(builder);
671         final String fileName = ConfigurationAssert.getTestFile("testValidateInvalid.xml").getAbsolutePath();
672         assertThrows(ConfigurationException.class, () -> load(conf, fileName));
673     }
674 
675     /**
676      * Tests whether a valid document can be loaded with a validating document builder.
677      */
678     @Test
679     public void testCustomDocBuilderValidationSuccess() throws Exception {
680         final DocumentBuilder builder = createValidatingDocBuilder();
681         conf = new XMLConfiguration();
682         conf.setDocumentBuilder(builder);
683         load(conf, ConfigurationAssert.getTestFile("testValidateValid.xml").getAbsolutePath());
684         assertTrue(conf.containsKey("table.fields.field(1).type"));
685     }
686 
687     /**
688      * Tests string properties with list delimiters when delimiter parsing is disabled
689      */
690     @Test
691     public void testDelimiterParsingDisabled() throws ConfigurationException {
692         final XMLConfiguration conf2 = new XMLConfiguration();
693         load(conf2, testProperties);
694 
695         assertEquals("a,b,c", conf2.getString("split.list3[@values]"));
696         assertEquals(0, conf2.getMaxIndex("split.list3[@values]"));
697         assertEquals("a\\,b\\,c", conf2.getString("split.list4[@values]"));
698         assertEquals("a,b,c", conf2.getString("split.list1"));
699         assertEquals(0, conf2.getMaxIndex("split.list1"));
700         assertEquals("a\\,b\\,c", conf2.getString("split.list2"));
701     }
702 
703     /**
704      * Tests whether string properties with list delimiters can be accessed if delimiter parsing is disabled and the XPath
705      * expression engine is used.
706      */
707     @Test
708     public void testDelimiterParsingDisabledXPath() throws ConfigurationException {
709         final XMLConfiguration conf2 = new XMLConfiguration();
710         conf2.setExpressionEngine(new XPathExpressionEngine());
711         load(conf2, testProperties);
712 
713         assertEquals("a,b,c", conf2.getString("split/list3/@values"));
714         assertEquals(0, conf2.getMaxIndex("split/list3/@values"));
715         assertEquals("a\\,b\\,c", conf2.getString("split/list4/@values"));
716         assertEquals("a,b,c", conf2.getString("split/list1"));
717         assertEquals(0, conf2.getMaxIndex("split/list1"));
718         assertEquals("a\\,b\\,c", conf2.getString("split/list2"));
719     }
720 
721     /**
722      * Tests whether a DTD can be accessed.
723      */
724     @Test
725     public void testDtd() throws ConfigurationException {
726         conf = new XMLConfiguration();
727         load(conf, "testDtd.xml");
728         assertEquals("value1", conf.getString("entry(0)"));
729         assertEquals("test2", conf.getString("entry(1)[@key]"));
730     }
731 
732     /**
733      * Tests whether an attribute can be set to an empty string. This test is related to CONFIGURATION-446.
734      */
735     @Test
736     public void testEmptyAttribute() throws ConfigurationException {
737         final String key = "element3[@value]";
738         conf.setProperty(key, "");
739         assertTrue(conf.containsKey(key));
740         assertEquals("", conf.getString(key));
741         saveTestConfig();
742         conf = new XMLConfiguration();
743         load(conf, testSaveConf.getAbsolutePath());
744         assertTrue(conf.containsKey(key));
745         assertEquals("", conf.getString(key));
746     }
747 
748     /**
749      * Tests handling of empty elements.
750      */
751     @Test
752     public void testEmptyElements() throws ConfigurationException {
753         assertTrue(conf.containsKey("empty"));
754         assertEquals("", conf.getString("empty"));
755         conf.addProperty("empty2", "");
756         conf.setProperty("empty", "no more empty");
757         saveTestConfig();
758 
759         conf = new XMLConfiguration();
760         load(conf, testSaveConf.getAbsolutePath());
761         assertEquals("no more empty", conf.getString("empty"));
762         assertEquals("", conf.getProperty("empty2"));
763     }
764 
765     /**
766      * Tests the isEmpty() method for an empty configuration that was reloaded.
767      */
768     @Test
769     public void testEmptyReload() throws ConfigurationException {
770         conf = new XMLConfiguration();
771         assertTrue(conf.isEmpty());
772         saveTestConfig();
773         load(conf, testSaveConf.getAbsolutePath());
774         assertTrue(conf.isEmpty());
775     }
776 
777     @Test
778     public void testGetAttribute() {
779         assertEquals("foo", conf.getProperty("element3[@name]"));
780     }
781 
782     @Test
783     public void testGetCommentedProperty() {
784         assertEquals("", conf.getProperty("test.comment"));
785     }
786 
787     @Test
788     public void testGetComplexProperty() {
789         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
790     }
791 
792     @Test
793     public void testgetProperty() {
794         // test non-leaf element
795         Object property = conf.getProperty("clear");
796         assertNull(property);
797 
798         // test non-existent element
799         property = conf.getProperty("e");
800         assertNull(property);
801 
802         // test non-existent element
803         property = conf.getProperty("element3[@n]");
804         assertNull(property);
805 
806         // test single element
807         property = conf.getProperty("element");
808         assertInstanceOf(String.class, property);
809         assertEquals("value", property);
810 
811         // test single attribute
812         property = conf.getProperty("element3[@name]");
813         assertInstanceOf(String.class, property);
814         assertEquals("foo", property);
815 
816         // test non-text/cdata element
817         property = conf.getProperty("test.comment");
818         assertEquals("", property);
819 
820         // test cdata element
821         property = conf.getProperty("test.cdata");
822         assertInstanceOf(String.class, property);
823         assertEquals("<cdata value>", property);
824 
825         // test multiple sibling elements
826         property = conf.getProperty("list.sublist.item");
827         List<?> list = assertInstanceOf(List.class, property);
828         assertEquals(Arrays.asList("five", "six"), list);
829 
830         // test multiple, disjoined elements
831         property = conf.getProperty("list.item");
832         list = assertInstanceOf(List.class, property);
833         assertEquals(Arrays.asList("one", "two", "three", "four"), list);
834 
835         // test multiple, disjoined attributes
836         property = conf.getProperty("list.item[@name]");
837         list = assertInstanceOf(List.class, property);
838         assertEquals(Arrays.asList("one", "three"), list);
839     }
840 
841     @Test
842     public void testGetProperty() {
843         assertEquals("value", conf.getProperty("element"));
844     }
845 
846     @Test
847     public void testGetPropertyWithXMLEntity() {
848         assertEquals("1<2", conf.getProperty("test.entity"));
849     }
850 
851     /**
852      * Tests the copy constructor.
853      */
854     @Test
855     public void testInitCopy() throws ConfigurationException {
856         final XMLConfiguration copy = new XMLConfiguration(conf);
857         copy.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
858         assertEquals("value", copy.getProperty("element"));
859         assertNull(copy.getDocument());
860 
861         new FileHandler(copy).save(testSaveConf);
862         checkSavedConfig();
863     }
864 
865     /**
866      * Tests list nodes with multiple values and attributes.
867      */
868     @Test
869     public void testListWithAttributes() {
870         assertEquals(6, conf.getList("attrList.a").size());
871         assertEquals("ABC", conf.getString("attrList.a(0)"));
872         assertEquals("x", conf.getString("attrList.a(0)[@name]"));
873         assertEquals(6, conf.getList("attrList.a[@name]").size());
874     }
875 
876     /**
877      * Tests a list node with attributes that has multiple values separated by the list delimiter. In this scenario the
878      * attribute should be added to all list nodes.
879      */
880     @Test
881     public void testListWithAttributesMultiValue() {
882         assertEquals("1", conf.getString("attrList.a(1)"));
883         assertEquals("y", conf.getString("attrList.a(1)[@name]"));
884         for (int i = 1; i <= 3; i++) {
885             assertEquals(i, conf.getInt("attrList.a(" + i + ")"));
886             assertEquals("y", conf.getString("attrList.a(" + i + ")[@name]"));
887         }
888     }
889 
890     /**
891      * Tests a list node with multiple values and multiple attributes. All attribute values should be assigned to all list
892      * nodes.
893      */
894     @Test
895     public void testListWithMultipleAttributesMultiValue() {
896         for (int i = 1; i <= 2; i++) {
897             final String idxStr = String.format("(%d)", Integer.valueOf(i + 3));
898             final String nodeKey = "attrList.a" + idxStr;
899             assertEquals("value" + i, conf.getString(nodeKey));
900             assertEquals("u", conf.getString(nodeKey + "[@name]"));
901             assertEquals("yes", conf.getString(nodeKey + "[@test]"));
902         }
903     }
904 
905     /**
906      * Tests constructing an XMLConfiguration from a non existing file and later saving to this file.
907      */
908     @Test
909     public void testLoadAndSaveFromFile() throws Exception {
910         // If the file does not exist, an empty config is created
911         assertFalse(testSaveConf.exists());
912         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class, null, true);
913         builder.configure(new FileBasedBuilderParametersImpl().setFile(testSaveConf));
914         conf = builder.getConfiguration();
915         assertTrue(conf.isEmpty());
916         conf.addProperty("test", "yes");
917         builder.save();
918 
919         final XMLConfiguration checkConfig = createFromFile(testSaveConf.getAbsolutePath());
920         assertEquals("yes", checkConfig.getString("test"));
921     }
922 
923     @Test
924     public void testLoadChildNamespace() throws ConfigurationException {
925         conf = new XMLConfiguration();
926         new FileHandler(conf).load(ConfigurationAssert.getTestFile("testChildNamespace.xml"));
927         assertEquals("http://example.com/", conf.getString("foo:bar.[@xmlns:foo]"));
928     }
929 
930     /**
931      * Tests loading from a stream.
932      */
933     @Test
934     public void testLoadFromStream() throws Exception {
935         final String xml = "<?xml version=\"1.0\"?><config><test>1</test></config>";
936         conf = new XMLConfiguration();
937         FileHandler handler = new FileHandler(conf);
938         handler.load(new ByteArrayInputStream(xml.getBytes()));
939         assertEquals(1, conf.getInt("test"));
940 
941         conf = new XMLConfiguration();
942         handler = new FileHandler(conf);
943         handler.load(new ByteArrayInputStream(xml.getBytes()), "UTF8");
944         assertEquals(1, conf.getInt("test"));
945     }
946 
947     /**
948      * Tests loading a non well formed XML from a string.
949      */
950     @Test
951     public void testLoadInvalidXML() throws Exception {
952         final String xml = "<?xml version=\"1.0\"?><config><test>1</rest></config>";
953         conf = new XMLConfiguration();
954         final FileHandler handler = new FileHandler(conf);
955         final StringReader reader = new StringReader(xml);
956         assertThrows(ConfigurationException.class, () -> handler.load(reader));
957     }
958 
959     /**
960      * Tests whether the encoding is correctly detected by the XML parser. This is done by loading an XML file with the
961      * encoding "UTF-16". If this encoding is not detected correctly, an exception will be thrown that "Content is not
962      * allowed in prolog". This test case is related to issue 34204.
963      */
964     @Test
965     public void testLoadWithEncoding() throws ConfigurationException {
966         conf = new XMLConfiguration();
967         new FileHandler(conf).load(ConfigurationAssert.getTestFile("testEncoding.xml"));
968         assertEquals("test3_yoge", conf.getString("yoge"));
969     }
970 
971     @Test
972     public void testLoadWithRootNamespace() throws ConfigurationException {
973         conf = new XMLConfiguration();
974         new FileHandler(conf).load(ConfigurationAssert.getTestFile("testRootNamespace.xml"));
975         assertEquals("http://example.com/", conf.getString("[@xmlns:foo]"));
976     }
977 
978     /**
979      * Tests that attribute values are not split.
980      */
981     @Test
982     public void testNoDelimiterParsingInAttrValues() throws ConfigurationException {
983         conf.clear();
984         load(conf, testProperties);
985         final List<Object> expr = conf.getList("expressions[@value]");
986         assertEquals(Arrays.asList("a || (b && c) | !d"), expr);
987     }
988 
989     /**
990      * Tests whether an attribute value can be overridden.
991      */
992     @Test
993     public void testOverrideAttribute() {
994         conf.addProperty("element3[@name]", "bar");
995 
996         final List<Object> list = conf.getList("element3[@name]");
997         assertEquals(Arrays.asList("bar"), list);
998     }
999 
1000     /**
1001      * Tests whether spaces are preserved when the xml:space attribute is set.
1002      */
1003     @Test
1004     public void testPreserveSpace() {
1005         assertEquals(" ", conf.getString("space.blank"));
1006         assertEquals(" * * ", conf.getString("space.stars"));
1007     }
1008 
1009     /**
1010      * Tests an xml:space attribute with an invalid value. This will be interpreted as default.
1011      */
1012     @Test
1013     public void testPreserveSpaceInvalid() {
1014         assertEquals("Some other text", conf.getString("space.testInvalid"));
1015     }
1016 
1017     /**
1018      * Tests whether the xml:space attribute works directly on the current element. This test is related to
1019      * CONFIGURATION-555.
1020      */
1021     @Test
1022     public void testPreserveSpaceOnElement() {
1023         assertEquals(" preserved ", conf.getString("spaceElement"));
1024         assertEquals("   ", conf.getString("spaceBlankElement"));
1025     }
1026 
1027     /**
1028      * Tests whether the xml:space attribute can be overridden in nested elements.
1029      */
1030     @Test
1031     public void testPreserveSpaceOverride() {
1032         assertEquals("Some text", conf.getString("space.description"));
1033     }
1034 
1035     /**
1036      * Tests whether the public ID is accessed in a synchronized manner.
1037      */
1038     @Test
1039     public void testPublicIdSynchronized() {
1040         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
1041         conf.setSynchronizer(sync);
1042         conf.setPublicID(PUBLIC_ID);
1043         assertEquals(PUBLIC_ID, conf.getPublicID());
1044         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ, Methods.END_READ);
1045     }
1046 
1047     /**
1048      * Tests a direct invocation of the read() method. This is not allowed because certain initializations have not been
1049      * done. This test is related to CONFIGURATION-641.
1050      */
1051     @Test
1052     public void testReadCalledDirectly() {
1053         conf = new XMLConfiguration();
1054         final String content = "<configuration><test>1</test></configuration>";
1055         final ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes());
1056         final ConfigurationException e = assertThrows(ConfigurationException.class, () -> conf.read(bis));
1057         assertThat(e.getMessage(), containsString("FileHandler"));
1058     }
1059 
1060     @Test
1061     public void testSave() throws Exception {
1062         // add an array of strings to the configuration
1063         conf.addProperty("string", "value1");
1064         for (int i = 1; i < 5; i++) {
1065             conf.addProperty("test.array", "value" + i);
1066         }
1067 
1068         // add comma delimited lists with escaped delimiters
1069         conf.addProperty("split.list5", "a\\,b\\,c");
1070         conf.setProperty("element3", "value\\,value1\\,value2");
1071         conf.setProperty("element3[@name]", "foo\\,bar");
1072 
1073         // save the configuration
1074         saveTestConfig();
1075 
1076         // read the configuration and compare the properties
1077         checkSavedConfig();
1078     }
1079 
1080     /**
1081      * Tests saving a configuration that was created from a hierarchical configuration. This test exposes bug
1082      * CONFIGURATION-301.
1083      */
1084     @Test
1085     public void testSaveAfterCreateWithCopyConstructor() throws ConfigurationException {
1086         final HierarchicalConfiguration<ImmutableNode> hc = conf.configurationAt("element2");
1087         conf = new XMLConfiguration(hc);
1088         saveTestConfig();
1089         final XMLConfiguration checkConfig = checkSavedConfig();
1090         assertEquals("element2", checkConfig.getRootElementName());
1091     }
1092 
1093     /**
1094      * Tests saving attributes (related to issue 34442).
1095      */
1096     @Test
1097     public void testSaveAttributes() throws Exception {
1098         conf.clear();
1099         load(conf, testProperties);
1100         saveTestConfig();
1101         conf = new XMLConfiguration();
1102         load(conf, testSaveConf.getAbsolutePath());
1103         assertEquals("foo", conf.getString("element3[@name]"));
1104     }
1105 
1106     /**
1107      * Tests saving and loading a configuration when delimiter parsing is disabled.
1108      */
1109     @Test
1110     public void testSaveDelimiterParsingDisabled() throws ConfigurationException {
1111         checkSaveDelimiterParsingDisabled("list.delimiter.test");
1112     }
1113 
1114     /**
1115      * Tests saving to a stream.
1116      */
1117     @Test
1118     public void testSaveToStream() throws ConfigurationException, IOException {
1119         final FileHandler handler = new FileHandler(conf);
1120         try (FileOutputStream out = new FileOutputStream(testSaveConf)) {
1121             handler.save(out, "UTF8");
1122         }
1123 
1124         checkSavedConfig(testSaveConf);
1125     }
1126 
1127     /**
1128      * Tests whether a configuration can be saved to a stream with a specific encoding.
1129      */
1130     @Test
1131     public void testSaveToStreamWithEncoding() throws ConfigurationException, IOException {
1132         final FileHandler handler = new FileHandler(conf);
1133         handler.setEncoding("UTF8");
1134         try (FileOutputStream out = new FileOutputStream(testSaveConf)) {
1135             handler.save(out);
1136         }
1137 
1138         checkSavedConfig(testSaveConf);
1139     }
1140 
1141     /**
1142      * Tests saving to a URL.
1143      */
1144     @Test
1145     public void testSaveToURL() throws Exception {
1146         final FileHandler handler = new FileHandler(conf);
1147         handler.save(testSaveConf.toURI().toURL());
1148         checkSavedConfig(testSaveConf);
1149     }
1150 
1151     /**
1152      * Tests whether a windows path can be saved correctly. This test is related to CONFIGURATION-428.
1153      */
1154     @Test
1155     public void testSaveWindowsPath() throws ConfigurationException {
1156         conf.clear();
1157         conf.setListDelimiterHandler(new DisabledListDelimiterHandler());
1158         conf.addProperty("path", "C:\\Temp");
1159         final StringWriter writer = new StringWriter();
1160         new FileHandler(conf).save(writer);
1161         final String content = writer.toString();
1162         assertThat("Path not found: ", content, containsString("<path>C:\\Temp</path>"));
1163         saveTestConfig();
1164         final XMLConfiguration conf2 = new XMLConfiguration();
1165         load(conf2, testSaveConf.getAbsolutePath());
1166         assertEquals("C:\\Temp", conf2.getString("path"));
1167     }
1168 
1169     /**
1170      * Tests string properties with list delimiters when delimiter parsing is disabled
1171      */
1172     @Test
1173     public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException {
1174         conf = new XMLConfiguration();
1175         conf.setExpressionEngine(new XPathExpressionEngine());
1176         load(conf, testProperties);
1177 
1178         assertEquals("a,b,c", conf.getString("split/list3/@values"));
1179         assertEquals(0, conf.getMaxIndex("split/list3/@values"));
1180         assertEquals("a\\,b\\,c", conf.getString("split/list4/@values"));
1181         assertEquals("a,b,c", conf.getString("split/list1"));
1182         assertEquals(0, conf.getMaxIndex("split/list1"));
1183         assertEquals("a\\,b\\,c", conf.getString("split/list2"));
1184         // save the configuration
1185         saveTestConfig();
1186 
1187         XMLConfiguration config = new XMLConfiguration();
1188         // config.setExpressionEngine(new XPathExpressionEngine());
1189         load(config, testFile2);
1190         config.setProperty("Employee[@attr1]", "3,2,1");
1191         assertEquals("3,2,1", config.getString("Employee[@attr1]"));
1192         new FileHandler(config).save(testSaveFile);
1193         config = new XMLConfiguration();
1194         // config.setExpressionEngine(new XPathExpressionEngine());
1195         load(config, testSaveFile.getAbsolutePath());
1196         config.setProperty("Employee[@attr1]", "1,2,3");
1197         assertEquals("1,2,3", config.getString("Employee[@attr1]"));
1198         config.setProperty("Employee[@attr2]", "one, two, three");
1199         assertEquals("one, two, three", config.getString("Employee[@attr2]"));
1200         config.setProperty("Employee.text", "a,b,d");
1201         assertEquals("a,b,d", config.getString("Employee.text"));
1202         config.setProperty("Employee.Salary", "100,000");
1203         assertEquals("100,000", config.getString("Employee.Salary"));
1204         new FileHandler(config).save(testSaveFile);
1205         final XMLConfiguration checkConfig = new XMLConfiguration();
1206         checkConfig.setExpressionEngine(new XPathExpressionEngine());
1207         load(checkConfig, testSaveFile.getAbsolutePath());
1208         assertEquals("1,2,3", checkConfig.getString("Employee/@attr1"));
1209         assertEquals("one, two, three", checkConfig.getString("Employee/@attr2"));
1210         assertEquals("a,b,d", checkConfig.getString("Employee/text"));
1211         assertEquals("100,000", checkConfig.getString("Employee/Salary"));
1212     }
1213 
1214     /**
1215      * Tests whether the DOCTYPE survives a save operation.
1216      */
1217     @Test
1218     public void testSaveWithDoctype() throws ConfigurationException {
1219         conf = new XMLConfiguration();
1220         load(conf, "testDtdPublic.xml");
1221 
1222         assertEquals(PUBLIC_ID, conf.getPublicID());
1223         assertEquals(SYSTEM_ID, conf.getSystemID());
1224         final StringWriter out = new StringWriter();
1225         new FileHandler(conf).save(out);
1226         assertThat(out.toString(), containsString(DOCTYPE));
1227     }
1228 
1229     /**
1230      * Tests setting public and system IDs for the DOCTYPE and then saving the configuration. This should generate a DOCTYPE
1231      * declaration.
1232      */
1233     @Test
1234     public void testSaveWithDoctypeIDs() throws ConfigurationException {
1235         assertNull(conf.getPublicID());
1236         assertNull(conf.getSystemID());
1237         conf.setPublicID(PUBLIC_ID);
1238         conf.setSystemID(SYSTEM_ID);
1239         final StringWriter out = new StringWriter();
1240         new FileHandler(conf).save(out);
1241         assertThat(out.toString(), containsString(DOCTYPE + "testconfig" + DOCTYPE_DECL));
1242     }
1243 
1244     /**
1245      * Tests whether the encoding is written to the generated XML file.
1246      */
1247     @Test
1248     public void testSaveWithEncoding() throws ConfigurationException {
1249         conf = new XMLConfiguration();
1250         conf.setProperty("test", "a value");
1251         final FileHandler handler = new FileHandler(conf);
1252         handler.setEncoding(ENCODING);
1253 
1254         final StringWriter out = new StringWriter();
1255         handler.save(out);
1256         assertThat(out.toString(), containsString("encoding=\"" + ENCODING + "\""));
1257     }
1258 
1259     /**
1260      * Tests saving a configuration if an invalid transformer factory is specified. In this case an error is thrown by the
1261      * transformer factory. XMLConfiguration should not catch this error.
1262      */
1263     @Test
1264     public void testSaveWithInvalidTransformerFactory() {
1265         System.setProperty(PROP_FACTORY, "an.invalid.Class");
1266         try {
1267             assertThrows(TransformerFactoryConfigurationError.class, this::saveTestConfig);
1268         } finally {
1269             System.getProperties().remove(PROP_FACTORY);
1270         }
1271     }
1272 
1273     /**
1274      * Tests whether a default encoding is used if no specific encoding is set. According to the XSLT specification
1275      * (http://www.w3.org/TR/xslt#output) this should be either UTF-8 or UTF-16.
1276      */
1277     @Test
1278     public void testSaveWithNullEncoding() throws ConfigurationException {
1279         conf = new XMLConfiguration();
1280         conf.setProperty("testNoEncoding", "yes");
1281         final FileHandler handler = new FileHandler(conf);
1282 
1283         final StringWriter out = new StringWriter();
1284         handler.save(out);
1285         assertThat("Encoding was written to file", out.toString(), containsString("encoding=\"UTF-"));
1286     }
1287 
1288     @Test
1289     public void testSaveWithRootAttributes() throws ConfigurationException {
1290         conf.setProperty("[@xmlns:ex]", "http://example.com/");
1291         assertEquals("http://example.com/", conf.getString("[@xmlns:ex]"));
1292         final FileHandler handler = new FileHandler(conf);
1293 
1294         final StringWriter out = new StringWriter();
1295         handler.save(out);
1296         assertThat("Encoding was not written to file", out.toString(), containsString("testconfig xmlns:ex=\"http://example.com/\""));
1297     }
1298 
1299     @Test
1300     public void testSaveWithRootAttributes_ByHand() throws ConfigurationException {
1301         conf = new XMLConfiguration();
1302         conf.addProperty("[@xmlns:foo]", "http://example.com/");
1303         assertEquals("http://example.com/", conf.getString("[@xmlns:foo]"));
1304         final FileHandler handler = new FileHandler(conf);
1305 
1306         final StringWriter out = new StringWriter();
1307         handler.save(out);
1308         assertThat("Encoding was not written to file", out.toString(), containsString("configuration xmlns:foo=\"http://example.com/\""));
1309     }
1310 
1311     /**
1312      * Tests modifying an XML document and saving it with schema validation enabled.
1313      */
1314     @Test
1315     public void testSaveWithValidation() throws Exception {
1316         final CatalogResolver resolver = new CatalogResolver();
1317         resolver.setCatalogFiles(CATALOG_FILES);
1318         conf = new XMLConfiguration();
1319         conf.setEntityResolver(resolver);
1320         conf.setSchemaValidation(true);
1321         load(conf, testFile2);
1322         conf.setProperty("Employee.SSN", "123456789");
1323         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
1324         conf.setSynchronizer(sync);
1325         conf.validate();
1326         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
1327         saveTestConfig();
1328         conf = new XMLConfiguration();
1329         load(conf, testSaveConf.getAbsolutePath());
1330         assertEquals("123456789", conf.getString("Employee.SSN"));
1331     }
1332 
1333     /**
1334      * Tests modifying an XML document and validating it against the schema.
1335      */
1336     @Test
1337     public void testSaveWithValidationFailure() throws Exception {
1338         final CatalogResolver resolver = new CatalogResolver();
1339         resolver.setCatalogFiles(CATALOG_FILES);
1340         conf = new XMLConfiguration();
1341         conf.setEntityResolver(resolver);
1342         conf.setSchemaValidation(true);
1343         load(conf, testFile2);
1344         conf.setProperty("Employee.Email", "JohnDoe@test.org");
1345         final Exception e = assertThrows(Exception.class, conf::validate);
1346         final Throwable cause = e.getCause();
1347         assertInstanceOf(SAXParseException.class, cause);
1348     }
1349 
1350     @Test
1351     public void testSetAttribute() {
1352         // replace an existing attribute
1353         conf.setProperty("element3[@name]", "bar");
1354         assertEquals("bar", conf.getProperty("element3[@name]"));
1355 
1356         // set a new attribute
1357         conf.setProperty("foo[@bar]", "value");
1358         assertEquals("value", conf.getProperty("foo[@bar]"));
1359 
1360         conf.setProperty("name1", "value1");
1361         assertEquals("value1", conf.getProperty("name1"));
1362     }
1363 
1364     @Test
1365     public void testSetProperty() throws Exception {
1366         conf.setProperty("element.string", "hello");
1367 
1368         assertEquals("hello", conf.getString("element.string"));
1369         assertEquals("hello", conf.getProperty("element.string"));
1370     }
1371 
1372     /**
1373      * Tests whether list properties are set correctly if delimiter parsing is disabled. This test is related to
1374      * CONFIGURATION-495.
1375      */
1376     @Test
1377     public void testSetPropertyListWithDelimiterParsingDisabled() throws ConfigurationException {
1378         final String prop = "delimiterListProp";
1379         final List<String> list = Arrays.asList("val", "val2", "val3");
1380         conf.setProperty(prop, list);
1381         saveTestConfig();
1382         final XMLConfiguration conf2 = new XMLConfiguration();
1383         load(conf2, testSaveConf.getAbsolutePath());
1384         assertEquals(list, conf2.getProperty(prop));
1385     }
1386 
1387     /**
1388      * Tests setting an attribute on the root element.
1389      */
1390     @Test
1391     public void testSetRootAttribute() throws ConfigurationException {
1392         conf.setProperty("[@test]", "true");
1393         assertEquals("true", conf.getString("[@test]"));
1394         saveTestConfig();
1395         XMLConfiguration checkConf = checkSavedConfig();
1396         assertTrue(checkConf.containsKey("[@test]"));
1397         checkConf.setProperty("[@test]", "newValue");
1398         conf = checkConf;
1399         saveTestConfig();
1400         checkConf = checkSavedConfig();
1401         assertEquals("newValue", checkConf.getString("[@test]"));
1402     }
1403 
1404     @Test
1405     public void testSetRootNamespace() throws ConfigurationException {
1406         conf.addProperty("[@xmlns:foo]", "http://example.com/");
1407         conf.addProperty("foo:bar", "foobar");
1408         assertEquals("http://example.com/", conf.getString("[@xmlns:foo]"));
1409         saveTestConfig();
1410         final XMLConfiguration checkConf = checkSavedConfig();
1411         assertTrue(checkConf.containsKey("[@xmlns:foo]"));
1412         checkConf.setProperty("[@xmlns:foo]", "http://example.net/");
1413     }
1414 
1415     /**
1416      * Tests setting text of the root element.
1417      */
1418     @Test
1419     public void testSetTextRootElement() throws ConfigurationException {
1420         conf.setProperty("", "Root text");
1421         saveTestConfig();
1422         checkSavedConfig();
1423     }
1424 
1425     /**
1426      * Tests string properties with list delimiters and escaped delimiters.
1427      */
1428     @Test
1429     public void testSplitLists() {
1430         assertEquals("a,b,c", conf.getString("split.list3[@values]"));
1431         assertEquals(0, conf.getMaxIndex("split.list3[@values]"));
1432         assertEquals("a\\,b\\,c", conf.getString("split.list4[@values]"));
1433         assertEquals("a", conf.getString("split.list1"));
1434         assertEquals(2, conf.getMaxIndex("split.list1"));
1435         assertEquals("a,b,c", conf.getString("split.list2"));
1436     }
1437 
1438     /**
1439      * Tests the subset() method. There was a bug that calling subset() had undesired side effects.
1440      */
1441     @Test
1442     public void testSubset() throws ConfigurationException {
1443         conf = new XMLConfiguration();
1444         load(conf, "testHierarchicalXMLConfiguration.xml");
1445         conf.subset("tables.table(0)");
1446         saveTestConfig();
1447 
1448         conf = new XMLConfiguration();
1449         load(conf, "testHierarchicalXMLConfiguration.xml");
1450         assertEquals("users", conf.getString("tables.table(0).name"));
1451     }
1452 
1453     /**
1454      * Tests whether the system ID is accessed in a synchronized manner.
1455      */
1456     @Test
1457     public void testSystemIdSynchronized() {
1458         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
1459         conf.setSynchronizer(sync);
1460         conf.setSystemID(SYSTEM_ID);
1461         assertEquals(SYSTEM_ID, conf.getSystemID());
1462         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ, Methods.END_READ);
1463     }
1464 
1465     /**
1466      * Tests DTD validation using the setValidating() method.
1467      */
1468     @Test
1469     public void testValidating() throws ConfigurationException {
1470         final File nonValidFile = ConfigurationAssert.getTestFile("testValidateInvalid.xml");
1471         conf = new XMLConfiguration();
1472         assertFalse(conf.isValidating());
1473 
1474         // Load a non valid XML document. Should work for isValidating() == false
1475         load(conf, nonValidFile.getAbsolutePath());
1476         assertEquals("customers", conf.getString("table.name"));
1477         assertFalse(conf.containsKey("table.fields.field(1).type"));
1478     }
1479 
1480     /**
1481      * Tests whether an invalid file is detected when validating is enabled.
1482      */
1483     @Test
1484     public void testValidatingInvalidFile() {
1485         conf = new XMLConfiguration();
1486         conf.setValidating(true);
1487         assertThrows(ConfigurationException.class, () -> load(conf, "testValidateInvalid.xml"));
1488     }
1489 
1490     @Test
1491     public void testWrite() throws Exception {
1492         final XMLConfiguration xmlConfig = new XMLConfiguration();
1493         xmlConfig.setRootElementName("IAmRoot");
1494         final StringWriter sw = new StringWriter();
1495         xmlConfig.write(sw);
1496         // Check that we can parse the XML.
1497         assertNotNull(parseXml(sw.toString()));
1498     }
1499 
1500     @Test
1501     public void testWriteIndentSize() throws Exception {
1502         final XMLConfiguration xmlConfig = new XMLConfiguration();
1503         xmlConfig.setRootElementName("IAmRoot");
1504         final StringWriter sw = new StringWriter();
1505         xmlConfig.setProperty("Child", "Alexander");
1506         xmlConfig.write(sw);
1507         // Check that we can parse the XML.
1508         final String xml = sw.toString();
1509         assertNotNull(parseXml(xml));
1510         final String indent = StringUtils.repeat(' ', XMLConfiguration.DEFAULT_INDENT_SIZE);
1511         assertTrue(xml.contains(System.lineSeparator() + indent + "<Child>"));
1512     }
1513 
1514     @Test
1515     public void testWriteWithTransformer() throws Exception {
1516         final XMLConfiguration xmlConfig = new XMLConfiguration();
1517         xmlConfig.setRootElementName("IAmRoot");
1518         xmlConfig.setProperty("Child", "Alexander");
1519         final StringWriter sw = new StringWriter();
1520         final Transformer transformer = xmlConfig.createTransformer();
1521         final int indentSize = 8;
1522         transformer.setOutputProperty(XMLConfiguration.INDENT_AMOUNT_PROPERTY, Integer.toString(indentSize));
1523         xmlConfig.write(sw, transformer);
1524         final String xml = sw.toString();
1525         assertNotNull(parseXml(xml));
1526         final String indent = StringUtils.repeat(' ', indentSize);
1527         assertTrue(xml.contains(System.lineSeparator() + indent + "<Child>"));
1528     }
1529 
1530     /**
1531      * Tests accessing properties when the XPATH expression engine is set.
1532      */
1533     @Test
1534     public void testXPathExpressionEngine() {
1535         conf.setExpressionEngine(new XPathExpressionEngine());
1536         assertEquals("foo\"bar", conf.getString("test[1]/entity/@name"));
1537         conf.clear();
1538         assertNull(conf.getString("test[1]/entity/@name"));
1539     }
1540 }