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.configuration;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertNotNull;
23  import static org.junit.Assert.assertNull;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.File;
29  import java.io.FileOutputStream;
30  import java.io.FileWriter;
31  import java.io.IOException;
32  import java.io.PrintWriter;
33  import java.io.StringReader;
34  import java.io.StringWriter;
35  import java.net.URL;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.Iterator;
40  import java.util.List;
41  
42  import javax.xml.parsers.DocumentBuilder;
43  import javax.xml.parsers.DocumentBuilderFactory;
44  import javax.xml.parsers.ParserConfigurationException;
45  
46  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
47  import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
48  import org.apache.commons.configuration.resolver.CatalogResolver;
49  import org.apache.commons.configuration.tree.ConfigurationNode;
50  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
51  import org.junit.Before;
52  import org.junit.Test;
53  import org.xml.sax.SAXException;
54  import org.xml.sax.SAXParseException;
55  import org.xml.sax.helpers.DefaultHandler;
56  
57  /**
58   * test for loading and saving xml properties files
59   *
60   * @version $Id: TestXMLConfiguration.java 1330666 2012-04-26 06:12:30Z oheger $
61   */
62  public class TestXMLConfiguration
63  {
64      /** XML Catalog */
65      private static final String CATALOG_FILES = ConfigurationAssert
66              .getTestFile("catalog.xml").getAbsolutePath();
67  
68      /** Constant for the used encoding.*/
69      static final String ENCODING = "ISO-8859-1";
70  
71      /** Constant for the test system ID.*/
72      static final String SYSTEM_ID = "properties.dtd";
73  
74      /** Constant for the test public ID.*/
75      static final String PUBLIC_ID = "-//Commons Configuration//DTD Test Configuration 1.3//EN";
76  
77      /** Constant for the DOCTYPE declaration.*/
78      static final String DOCTYPE_DECL = " PUBLIC \"" + PUBLIC_ID + "\" \"" + SYSTEM_ID + "\">";
79  
80      /** Constant for the DOCTYPE prefix.*/
81      static final String DOCTYPE = "<!DOCTYPE ";
82  
83      /** Constant for the transformer factory property.*/
84      static final String PROP_FACTORY = "javax.xml.transform.TransformerFactory";
85  
86      /** The File that we test with */
87      private String testProperties = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
88      private String testProperties2 = ConfigurationAssert.getTestFile("testDigesterConfigurationInclude1.xml").getAbsolutePath();
89      private String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
90      private File testSaveConf = ConfigurationAssert.getOutFile("testsave.xml");
91      private File testSaveFile = ConfigurationAssert.getOutFile("testsample2.xml");
92      private String testFile2 = ConfigurationAssert.getTestFile("sample.xml").getAbsolutePath();
93  
94      /** Constant for the number of test threads. */
95      private static final int THREAD_COUNT = 5;
96  
97      /** Constant for the number of loops in tests with multiple threads. */
98      private static final int LOOP_COUNT = 100;
99  
100     private XMLConfiguration conf;
101 
102     @Before
103     public void setUp() throws Exception
104     {
105         conf = new XMLConfiguration();
106         conf.setFile(new File(testProperties));
107         conf.load();
108         removeTestFile();
109     }
110 
111     @Test
112     public void testGetProperty()
113     {
114         assertEquals("value", conf.getProperty("element"));
115     }
116 
117     @Test
118     public void testGetCommentedProperty()
119     {
120         assertEquals("", conf.getProperty("test.comment"));
121     }
122 
123     @Test
124     public void testGetPropertyWithXMLEntity()
125     {
126         assertEquals("1<2", conf.getProperty("test.entity"));
127     }
128 
129     @Test
130     public void testClearProperty() throws Exception
131     {
132         // test non-existent element
133         String key = "clearly";
134         conf.clearProperty(key);
135         assertNull(key, conf.getProperty(key));
136         assertNull(key, conf.getProperty(key));
137 
138         // test single element
139         conf.load();
140         key = "clear.element";
141         conf.clearProperty(key);
142         assertNull(key, conf.getProperty(key));
143         assertNull(key, conf.getProperty(key));
144 
145         // test single element with attribute
146         conf.load();
147         key = "clear.element2";
148         conf.clearProperty(key);
149         assertNull(key, conf.getProperty(key));
150         assertNull(key, conf.getProperty(key));
151         key = "clear.element2[@id]";
152         assertNotNull(key, conf.getProperty(key));
153         assertNotNull(key, conf.getProperty(key));
154 
155         // test non-text/cdata element
156         conf.load();
157         key = "clear.comment";
158         conf.clearProperty(key);
159         assertNull(key, conf.getProperty(key));
160         assertNull(key, conf.getProperty(key));
161 
162         // test cdata element
163         conf.load();
164         key = "clear.cdata";
165         conf.clearProperty(key);
166         assertNull(key, conf.getProperty(key));
167         assertNull(key, conf.getProperty(key));
168 
169         // test multiple sibling elements
170         conf.load();
171         key = "clear.list.item";
172         conf.clearProperty(key);
173         assertNull(key, conf.getProperty(key));
174         assertNull(key, conf.getProperty(key));
175         key = "clear.list.item[@id]";
176         assertNotNull(key, conf.getProperty(key));
177         assertNotNull(key, conf.getProperty(key));
178 
179         // test multiple, disjoined elements
180         conf.load();
181         key = "list.item";
182         conf.clearProperty(key);
183         assertNull(key, conf.getProperty(key));
184         assertNull(key, conf.getProperty(key));
185     }
186 
187     @Test
188     public void testgetProperty() {
189         // test non-leaf element
190         Object property = conf.getProperty("clear");
191         assertNull(property);
192 
193         // test non-existent element
194         property = conf.getProperty("e");
195         assertNull(property);
196 
197         // test non-existent element
198         property = conf.getProperty("element3[@n]");
199         assertNull(property);
200 
201         // test single element
202         property = conf.getProperty("element");
203         assertNotNull(property);
204         assertTrue(property instanceof String);
205         assertEquals("value", property);
206 
207         // test single attribute
208         property = conf.getProperty("element3[@name]");
209         assertNotNull(property);
210         assertTrue(property instanceof String);
211         assertEquals("foo", property);
212 
213         // test non-text/cdata element
214         property = conf.getProperty("test.comment");
215         assertEquals("", property);
216 
217         // test cdata element
218         property = conf.getProperty("test.cdata");
219         assertNotNull(property);
220         assertTrue(property instanceof String);
221         assertEquals("<cdata value>", property);
222 
223         // test multiple sibling elements
224         property = conf.getProperty("list.sublist.item");
225         assertNotNull(property);
226         assertTrue(property instanceof List);
227         List<?> list = (List<?>) property;
228         assertEquals(2, list.size());
229         assertEquals("five", list.get(0));
230         assertEquals("six", list.get(1));
231 
232         // test multiple, disjoined elements
233         property = conf.getProperty("list.item");
234         assertNotNull(property);
235         assertTrue(property instanceof List);
236         list = (List<?>) property;
237         assertEquals(4, list.size());
238         assertEquals("one", list.get(0));
239         assertEquals("two", list.get(1));
240         assertEquals("three", list.get(2));
241         assertEquals("four", list.get(3));
242 
243         // test multiple, disjoined attributes
244         property = conf.getProperty("list.item[@name]");
245         assertNotNull(property);
246         assertTrue(property instanceof List);
247         list = (List<?>) property;
248         assertEquals(2, list.size());
249         assertEquals("one", list.get(0));
250         assertEquals("three", list.get(1));
251     }
252 
253     @Test
254     public void testGetAttribute()
255     {
256         assertEquals("element3[@name]", "foo", conf.getProperty("element3[@name]"));
257     }
258 
259     @Test
260     public void testClearAttribute() throws Exception
261     {
262         // test non-existent attribute
263         String key = "clear[@id]";
264         conf.clearProperty(key);
265         assertNull(key, conf.getProperty(key));
266         assertNull(key, conf.getProperty(key));
267 
268         // test single attribute
269         conf.load();
270         key = "clear.element2[@id]";
271         conf.clearProperty(key);
272         assertNull(key, conf.getProperty(key));
273         assertNull(key, conf.getProperty(key));
274         key = "clear.element2";
275         assertNotNull(key, conf.getProperty(key));
276         assertNotNull(key, conf.getProperty(key));
277 
278         // test multiple, disjoined attributes
279         conf.load();
280         key = "clear.list.item[@id]";
281         conf.clearProperty(key);
282         assertNull(key, conf.getProperty(key));
283         assertNull(key, conf.getProperty(key));
284         key = "clear.list.item";
285         assertNotNull(key, conf.getProperty(key));
286         assertNotNull(key, conf.getProperty(key));
287     }
288 
289     @Test
290     public void testSetAttribute()
291     {
292         // replace an existing attribute
293         conf.setProperty("element3[@name]", "bar");
294         assertEquals("element3[@name]", "bar", conf.getProperty("element3[@name]"));
295 
296         // set a new attribute
297         conf.setProperty("foo[@bar]", "value");
298         assertEquals("foo[@bar]", "value", conf.getProperty("foo[@bar]"));
299 
300         conf.setProperty("name1","value1");
301         assertEquals("value1",conf.getProperty("name1"));
302     }
303 
304     @Test
305     public void testAddAttribute()
306     {
307         conf.addProperty("element3[@name]", "bar");
308 
309         List<Object> list = conf.getList("element3[@name]");
310         assertNotNull("null list", list);
311         assertTrue("'foo' element missing", list.contains("foo"));
312         assertTrue("'bar' element missing", list.contains("bar"));
313         assertEquals("list size", 2, list.size());
314     }
315 
316     @Test
317     public void testAddObjectAttribute()
318     {
319         conf.addProperty("test.boolean[@value]", Boolean.TRUE);
320         assertTrue("test.boolean[@value]", conf.getBoolean("test.boolean[@value]"));
321     }
322 
323     /**
324      * Tests setting an attribute on the root element.
325      */
326     @Test
327     public void testSetRootAttribute() throws ConfigurationException
328     {
329         conf.setProperty("[@test]", "true");
330         assertEquals("Root attribute not set", "true", conf
331                 .getString("[@test]"));
332         conf.save(testSaveConf);
333         XMLConfiguration checkConf = new XMLConfiguration();
334         checkConf.setFile(testSaveConf);
335         checkSavedConfig(checkConf);
336         assertTrue("Attribute not found after save", checkConf
337                 .containsKey("[@test]"));
338         checkConf.setProperty("[@test]", "newValue");
339         checkConf.save();
340         conf = checkConf;
341         checkConf = new XMLConfiguration();
342         checkConf.setFile(testSaveConf);
343         checkSavedConfig(checkConf);
344         assertEquals("Attribute not modified after save", "newValue", checkConf
345                 .getString("[@test]"));
346     }
347 
348     /**
349      * Tests whether the configuration's root node is initialized with a
350      * reference to the corresponding XML element.
351      */
352     @Test
353     public void testGetRootReference()
354     {
355         assertNotNull("Root node has no reference", conf.getRootNode()
356                 .getReference());
357     }
358 
359     @Test
360     public void testAddList()
361     {
362         conf.addProperty("test.array", "value1");
363         conf.addProperty("test.array", "value2");
364 
365         List<Object> list = conf.getList("test.array");
366         assertNotNull("null list", list);
367         assertTrue("'value1' element missing", list.contains("value1"));
368         assertTrue("'value2' element missing", list.contains("value2"));
369         assertEquals("list size", 2, list.size());
370     }
371 
372     @Test
373     public void testGetComplexProperty()
374     {
375         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
376     }
377 
378     @Test
379     public void testSettingFileNames()
380     {
381         conf = new XMLConfiguration();
382         conf.setFileName(testProperties);
383         assertEquals(testProperties.toString(), conf.getFileName());
384 
385         conf.setBasePath(testBasePath);
386         conf.setFileName("hello.xml");
387         assertEquals("hello.xml", conf.getFileName());
388         assertEquals(testBasePath.toString(), conf.getBasePath());
389         assertEquals(new File(testBasePath, "hello.xml"), conf.getFile());
390 
391         conf.setBasePath(testBasePath);
392         conf.setFileName("subdir/hello.xml");
393         assertEquals("subdir/hello.xml", conf.getFileName());
394         assertEquals(testBasePath.toString(), conf.getBasePath());
395         assertEquals(new File(testBasePath, "subdir/hello.xml"), conf.getFile());
396     }
397 
398     @Test
399     public void testLoad() throws Exception
400     {
401         conf = new XMLConfiguration();
402         conf.setFileName(testProperties);
403         conf.load();
404 
405         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
406     }
407 
408     @Test
409     public void testLoadWithBasePath() throws Exception
410     {
411         conf = new XMLConfiguration();
412 
413         conf.setFileName("test.xml");
414         conf.setBasePath(testBasePath);
415         conf.load();
416 
417         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
418     }
419 
420     /**
421      * Tests constructing an XMLConfiguration from a non existing file and
422      * later saving to this file.
423      */
424     @Test
425     public void testLoadAndSaveFromFile() throws Exception
426     {
427         // If the file does not exist, an empty config is created
428         conf = new XMLConfiguration(testSaveConf);
429         assertTrue(conf.isEmpty());
430         conf.addProperty("test", "yes");
431         conf.save();
432 
433         conf = new XMLConfiguration(testSaveConf);
434         assertEquals("yes", conf.getString("test"));
435     }
436 
437     /**
438      * Tests loading a configuration from a URL.
439      */
440     @Test
441     public void testLoadFromURL() throws Exception
442     {
443         URL url = new File(testProperties).toURI().toURL();
444         conf = new XMLConfiguration(url);
445         assertEquals("value", conf.getProperty("element"));
446         assertEquals(url, conf.getURL());
447     }
448 
449     /**
450      * Tests loading from a stream.
451      */
452     @Test
453     public void testLoadFromStream() throws Exception
454     {
455         String xml = "<?xml version=\"1.0\"?><config><test>1</test></config>";
456         conf = new XMLConfiguration();
457         conf.load(new ByteArrayInputStream(xml.getBytes()));
458         assertEquals(1, conf.getInt("test"));
459 
460         conf = new XMLConfiguration();
461         conf.load(new ByteArrayInputStream(xml.getBytes()), "UTF8");
462         assertEquals(1, conf.getInt("test"));
463     }
464 
465     /**
466      * Tests loading a non well formed XML from a string.
467      */
468     @Test(expected = ConfigurationException.class)
469     public void testLoadInvalidXML() throws Exception
470     {
471         String xml = "<?xml version=\"1.0\"?><config><test>1</rest></config>";
472         conf = new XMLConfiguration();
473         conf.load(new StringReader(xml));
474     }
475 
476     @Test
477     public void testSetProperty() throws Exception
478     {
479         conf.setProperty("element.string", "hello");
480 
481         assertEquals("'element.string'", "hello", conf.getString("element.string"));
482         assertEquals("XML value of element.string", "hello", conf.getProperty("element.string"));
483     }
484 
485     @Test
486     public void testAddProperty()
487     {
488         // add a property to a non initialized xml configuration
489         XMLConfiguration config = new XMLConfiguration();
490         config.addProperty("test.string", "hello");
491 
492         assertEquals("'test.string'", "hello", config.getString("test.string"));
493     }
494 
495     @Test
496     public void testAddObjectProperty()
497     {
498         // add a non string property
499         conf.addProperty("test.boolean", Boolean.TRUE);
500         assertTrue("'test.boolean'", conf.getBoolean("test.boolean"));
501     }
502 
503     @Test
504     public void testSave() throws Exception
505     {
506         // add an array of strings to the configuration
507         conf.addProperty("string", "value1");
508         for (int i = 1; i < 5; i++)
509         {
510             conf.addProperty("test.array", "value" + i);
511         }
512 
513         // add an array of strings in an attribute
514         for (int i = 1; i < 5; i++)
515         {
516            conf.addProperty("test.attribute[@array]", "value" + i);
517         }
518 
519         // add comma delimited lists with escaped delimiters
520         conf.addProperty("split.list5", "a\\,b\\,c");
521         conf.setProperty("element3", "value\\,value1\\,value2");
522         conf.setProperty("element3[@name]", "foo\\,bar");
523 
524         // save the configuration
525         conf.save(testSaveConf.getAbsolutePath());
526 
527         // read the configuration and compare the properties
528         XMLConfiguration checkConfig = new XMLConfiguration();
529         checkConfig.setFileName(testSaveConf.getAbsolutePath());
530         checkSavedConfig(checkConfig);
531     }
532 
533     /**
534      * Tests saving to a URL.
535      */
536     @Test
537     public void testSaveToURL() throws Exception
538     {
539         conf.save(testSaveConf.toURI().toURL());
540         XMLConfiguration checkConfig = new XMLConfiguration();
541         checkConfig.setFile(testSaveConf);
542         checkSavedConfig(checkConfig);
543     }
544 
545     /**
546      * Tests saving to a stream.
547      */
548     @Test
549     public void testSaveToStream() throws Exception
550     {
551         assertNull(conf.getEncoding());
552         conf.setEncoding("UTF8");
553         FileOutputStream out = null;
554         try
555         {
556             out = new FileOutputStream(testSaveConf);
557             conf.save(out);
558         }
559         finally
560         {
561             if(out != null)
562             {
563                 out.close();
564             }
565         }
566 
567         XMLConfiguration checkConfig = new XMLConfiguration();
568         checkConfig.setFile(testSaveConf);
569         checkSavedConfig(checkConfig);
570 
571         try
572         {
573             out = new FileOutputStream(testSaveConf);
574             conf.save(out, "UTF8");
575         }
576         finally
577         {
578             if(out != null)
579             {
580                 out.close();
581             }
582         }
583 
584         checkConfig.clear();
585         checkSavedConfig(checkConfig);
586     }
587 
588     @Test
589     public void testAutoSave() throws Exception
590     {
591         conf.setFile(testSaveConf);
592         assertFalse(conf.isAutoSave());
593         conf.setAutoSave(true);
594         assertTrue(conf.isAutoSave());
595         conf.setProperty("autosave", "ok");
596 
597         // reload the configuration
598         XMLConfiguration conf2 = new XMLConfiguration(conf.getFile());
599         assertEquals("'autosave' property", "ok", conf2.getString("autosave"));
600 
601         conf.clearTree("clear");
602         conf2 = new XMLConfiguration(conf.getFile());
603         Configuration sub = conf2.subset("clear");
604         assertTrue(sub.isEmpty());
605     }
606 
607     /**
608      * Tests if a second file can be appended to a first.
609      */
610     @Test
611     public void testAppend() throws Exception
612     {
613         conf = new XMLConfiguration();
614         conf.setFileName(testProperties);
615         conf.load();
616         conf.load(testProperties2);
617         assertEquals("value", conf.getString("element"));
618         assertEquals("tasks", conf.getString("table.name"));
619 
620         conf.save(testSaveConf);
621         conf = new XMLConfiguration(testSaveConf);
622         assertEquals("value", conf.getString("element"));
623         assertEquals("tasks", conf.getString("table.name"));
624         assertEquals("application", conf.getString("table[@tableType]"));
625     }
626 
627     /**
628      * Tests saving attributes (related to issue 34442).
629      */
630     @Test
631     public void testSaveAttributes() throws Exception
632     {
633         conf.clear();
634         conf.load();
635         conf.save(testSaveConf);
636         conf = new XMLConfiguration();
637         conf.load(testSaveConf);
638         assertEquals("foo", conf.getString("element3[@name]"));
639     }
640 
641     /**
642      * Tests collaboration between XMLConfiguration and a reloading strategy.
643      */
644     @Test
645     public void testReloading() throws Exception
646     {
647         assertNotNull(conf.getReloadingStrategy());
648         assertTrue(conf.getReloadingStrategy() instanceof InvariantReloadingStrategy);
649         PrintWriter out = null;
650 
651         try
652         {
653             out = new PrintWriter(new FileWriter(testSaveConf));
654             out.println("<?xml version=\"1.0\"?><config><test>1</test></config>");
655             out.close();
656             out = null;
657             conf.setFile(testSaveConf);
658             FileAlwaysReloadingStrategy strategy = new FileAlwaysReloadingStrategy();
659             strategy.setRefreshDelay(100);
660             conf.setReloadingStrategy(strategy);
661             assertEquals(strategy, conf.getReloadingStrategy());
662             assertEquals("Wrong file monitored", testSaveConf.getAbsolutePath(),
663                     strategy.getMonitoredFile().getAbsolutePath());
664             conf.load();
665             assertEquals(1, conf.getInt("test"));
666 
667             out = new PrintWriter(new FileWriter(testSaveConf));
668             out.println("<?xml version=\"1.0\"?><config><test>2</test></config>");
669             out.close();
670             out = null;
671 
672             int value = conf.getInt("test");
673             assertEquals("No reloading performed", 2, value);
674         }
675         finally
676         {
677             if (out != null)
678             {
679                 out.close();
680             }
681         }
682     }
683 
684     @Test
685     public void testReloadingOOM() throws Exception
686     {
687         assertNotNull(conf.getReloadingStrategy());
688         assertTrue(conf.getReloadingStrategy() instanceof InvariantReloadingStrategy);
689         PrintWriter out = null;
690 
691         try
692         {
693             out = new PrintWriter(new FileWriter(testSaveConf));
694             out.println("<?xml version=\"1.0\"?><config><test>1</test></config>");
695             out.close();
696             out = null;
697             conf.setFile(testSaveConf);
698             FileAlwaysReloadingStrategy strategy = new FileAlwaysReloadingStrategy();
699             strategy.setRefreshDelay(100);
700             conf.setReloadingStrategy(strategy);
701             conf.load();
702             assertEquals(1, conf.getInt("test"));
703 
704             for (int i = 1; i < LOOP_COUNT; ++i)
705             {
706                assertEquals(1, conf.getInt("test"));
707             }
708         }
709         finally
710         {
711             if (out != null)
712             {
713                 out.close();
714             }
715         }
716     }
717 
718     /**
719      * Tests the refresh() method.
720      */
721     @Test
722     public void testRefresh() throws ConfigurationException
723     {
724         conf.setProperty("element", "anotherValue");
725         conf.refresh();
726         assertEquals("Wrong property after refresh", "value",
727                 conf.getString("element"));
728     }
729 
730     /**
731      * Tries to call refresh() when the configuration is not associated with a
732      * file.
733      */
734     @Test(expected = ConfigurationException.class)
735     public void testRefreshNoFile() throws ConfigurationException
736     {
737         conf = new XMLConfiguration();
738         conf.refresh();
739     }
740 
741     /**
742      * Tests access to tag names with delimiter characters.
743      */
744     @Test
745     public void testComplexNames()
746     {
747         assertEquals("Name with dot", conf.getString("complexNames.my..elem"));
748         assertEquals("Another dot", conf.getString("complexNames.my..elem.sub..elem"));
749     }
750 
751     /**
752      * Creates a validating document builder.
753      * @return the document builder
754      * @throws ParserConfigurationException if an error occurs
755      */
756     private DocumentBuilder createValidatingDocBuilder()
757             throws ParserConfigurationException
758     {
759         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
760         factory.setValidating(true);
761         DocumentBuilder builder = factory.newDocumentBuilder();
762         builder.setErrorHandler(new DefaultHandler() {
763             @Override
764             public void error(SAXParseException ex) throws SAXException
765             {
766                 throw ex;
767             }
768         });
769         return builder;
770     }
771 
772     /**
773      * Tests setting a custom document builder.
774      */
775     @Test
776     public void testCustomDocBuilder() throws Exception
777     {
778         // Load an invalid XML file with the default (non validating)
779         // doc builder. This should work...
780         conf = new XMLConfiguration();
781         conf.load(ConfigurationAssert.getTestFile("testValidateInvalid.xml"));
782         assertEquals("customers", conf.getString("table.name"));
783         assertFalse(conf.containsKey("table.fields.field(1).type"));
784     }
785 
786     /**
787      * Tests whether a validating document builder detects a validation error.
788      */
789     @Test(expected = ConfigurationException.class)
790     public void testCustomDocBuilderValidationError() throws Exception
791     {
792         DocumentBuilder builder = createValidatingDocBuilder();
793         conf = new XMLConfiguration();
794         conf.setDocumentBuilder(builder);
795         conf.load(new File("conf/testValidateInvalid.xml"));
796     }
797 
798     /**
799      * Tests whether a valid document can be loaded with a validating document builder.
800      */
801     @Test
802     public void testCustomDocBuilderValidationSuccess() throws Exception
803     {
804         DocumentBuilder builder = createValidatingDocBuilder();
805         conf = new XMLConfiguration();
806         conf.setDocumentBuilder(builder);
807         conf.load(ConfigurationAssert.getTestFile("testValidateValid.xml"));
808         assertTrue(conf.containsKey("table.fields.field(1).type"));
809     }
810 
811     /**
812      * Tests the clone() method.
813      */
814     @Test
815     public void testClone()
816     {
817         Configuration c = (Configuration) conf.clone();
818         assertTrue(c instanceof XMLConfiguration);
819         XMLConfiguration copy = (XMLConfiguration) c;
820         assertNotNull(conf.getDocument());
821         assertNull(copy.getDocument());
822         assertNotNull(conf.getFileName());
823         assertNull(copy.getFileName());
824 
825         copy.setProperty("element3", "clonedValue");
826         assertEquals("value", conf.getString("element3"));
827         conf.setProperty("element3[@name]", "originalFoo");
828         assertEquals("foo", copy.getString("element3[@name]"));
829     }
830 
831     /**
832      * Tests saving a configuration after cloning to ensure that the clone and
833      * the original are completely detached.
834      */
835     @Test
836     public void testCloneWithSave() throws ConfigurationException
837     {
838         XMLConfiguration c = (XMLConfiguration) conf.clone();
839         c.addProperty("test.newProperty", Boolean.TRUE);
840         conf.addProperty("test.orgProperty", Boolean.TRUE);
841         c.save(testSaveConf);
842         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
843         assertTrue("New property after clone() was not saved", c2
844                 .getBoolean("test.newProperty"));
845         assertFalse("Property of original config was saved", c2
846                 .containsKey("test.orgProperty"));
847     }
848 
849     /**
850      * Tests the subset() method. There was a bug that calling subset() had
851      * undesired side effects.
852      */
853     @Test
854     public void testSubset() throws ConfigurationException
855     {
856         conf = new XMLConfiguration();
857         conf.load(ConfigurationAssert.getTestFile("testHierarchicalXMLConfiguration.xml"));
858         conf.subset("tables.table(0)");
859         conf.save(testSaveConf);
860 
861         conf = new XMLConfiguration(testSaveConf);
862         assertEquals("users", conf.getString("tables.table(0).name"));
863     }
864 
865     /**
866      * Tests string properties with list delimiters and escaped delimiters.
867      */
868     @Test
869     public void testSplitLists()
870     {
871         assertEquals("a", conf.getString("split.list3[@values]"));
872         assertEquals(2, conf.getMaxIndex("split.list3[@values]"));
873         assertEquals("a,b,c", conf.getString("split.list4[@values]"));
874         assertEquals("a", conf.getString("split.list1"));
875         assertEquals(2, conf.getMaxIndex("split.list1"));
876         assertEquals("a,b,c", conf.getString("split.list2"));
877     }
878 
879     /**
880      * Tests string properties with list delimiters when delimiter parsing
881      * is disabled
882      */
883     @Test
884     public void testDelimiterParsingDisabled() throws ConfigurationException {
885         XMLConfiguration conf2 = new XMLConfiguration();
886         conf2.setDelimiterParsingDisabled(true);
887         conf2.setFile(new File(testProperties));
888         conf2.load();
889 
890         assertEquals("a,b,c", conf2.getString("split.list3[@values]"));
891         assertEquals(0, conf2.getMaxIndex("split.list3[@values]"));
892         assertEquals("a\\,b\\,c", conf2.getString("split.list4[@values]"));
893         assertEquals("a,b,c", conf2.getString("split.list1"));
894         assertEquals(0, conf2.getMaxIndex("split.list1"));
895         assertEquals("a\\,b\\,c", conf2.getString("split.list2"));
896         conf2 = new XMLConfiguration();
897         conf2.setExpressionEngine(new XPathExpressionEngine());
898         conf2.setDelimiterParsingDisabled(true);
899         conf2.setFile(new File(testProperties));
900         conf2.load();
901 
902         assertEquals("a,b,c", conf2.getString("split/list3/@values"));
903         assertEquals(0, conf2.getMaxIndex("split/list3/@values"));
904         assertEquals("a\\,b\\,c", conf2.getString("split/list4/@values"));
905         assertEquals("a,b,c", conf2.getString("split/list1"));
906         assertEquals(0, conf2.getMaxIndex("split/list1"));
907         assertEquals("a\\,b\\,c", conf2.getString("split/list2"));
908     }
909 
910      /**
911      * Tests string properties with list delimiters when delimiter parsing
912      * is disabled
913      */
914     @Test
915     public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException {
916         XMLConfiguration conf = new XMLConfiguration();
917         conf.setExpressionEngine(new XPathExpressionEngine());
918         conf.setDelimiterParsingDisabled(true);
919         conf.setAttributeSplittingDisabled(true);
920         conf.setFile(new File(testProperties));
921         conf.load();
922 
923         assertEquals("a,b,c", conf.getString("split/list3/@values"));
924         assertEquals(0, conf.getMaxIndex("split/list3/@values"));
925         assertEquals("a\\,b\\,c", conf.getString("split/list4/@values"));
926         assertEquals("a,b,c", conf.getString("split/list1"));
927         assertEquals(0, conf.getMaxIndex("split/list1"));
928         assertEquals("a\\,b\\,c", conf.getString("split/list2"));
929         // save the configuration
930         conf.save(testSaveConf.getAbsolutePath());
931 
932         // read the configuration and compare the properties
933         XMLConfiguration checkConfig = new XMLConfiguration();
934         checkConfig.setFileName(testSaveConf.getAbsolutePath());
935         checkSavedConfig(checkConfig);
936         XMLConfiguration config = new XMLConfiguration();
937         config.setFileName(testFile2);
938         //config.setExpressionEngine(new XPathExpressionEngine());
939         config.setDelimiterParsingDisabled(true);
940         config.setAttributeSplittingDisabled(true);
941         config.load();
942         config.setProperty("Employee[@attr1]", "3,2,1");
943         assertEquals("3,2,1", config.getString("Employee[@attr1]"));
944         config.save(testSaveFile.getAbsolutePath());
945         config = new XMLConfiguration();
946         config.setFileName(testSaveFile.getAbsolutePath());
947         //config.setExpressionEngine(new XPathExpressionEngine());
948         config.setDelimiterParsingDisabled(true);
949         config.setAttributeSplittingDisabled(true);
950         config.load();
951         config.setProperty("Employee[@attr1]", "1,2,3");
952         assertEquals("1,2,3", config.getString("Employee[@attr1]"));
953         config.setProperty("Employee[@attr2]", "one, two, three");
954         assertEquals("one, two, three", config.getString("Employee[@attr2]"));
955         config.setProperty("Employee.text", "a,b,d");
956         assertEquals("a,b,d", config.getString("Employee.text"));
957         config.setProperty("Employee.Salary", "100,000");
958         assertEquals("100,000", config.getString("Employee.Salary"));
959         config.save(testSaveFile.getAbsolutePath());
960         checkConfig = new XMLConfiguration();
961         checkConfig.setFileName(testSaveFile.getAbsolutePath());
962         checkConfig.setExpressionEngine(new XPathExpressionEngine());
963         checkConfig.setDelimiterParsingDisabled(true);
964         checkConfig.setAttributeSplittingDisabled(true);
965         checkConfig.load();
966         assertEquals("1,2,3", checkConfig.getString("Employee/@attr1"));
967         assertEquals("one, two, three", checkConfig.getString("Employee/@attr2"));
968         assertEquals("a,b,d", checkConfig.getString("Employee/text"));
969         assertEquals("100,000", checkConfig.getString("Employee/Salary"));
970     }
971 
972     /**
973      * Tests whether a DTD can be accessed.
974      */
975     @Test
976     public void testDtd() throws ConfigurationException
977     {
978         conf = new XMLConfiguration("testDtd.xml");
979         assertEquals("value1", conf.getString("entry(0)"));
980         assertEquals("test2", conf.getString("entry(1)[@key]"));
981     }
982 
983     /**
984      * Tests DTD validation using the setValidating() method.
985      */
986     @Test
987     public void testValidating() throws ConfigurationException
988     {
989         File nonValidFile = ConfigurationAssert.getTestFile("testValidateInvalid.xml");
990         conf = new XMLConfiguration();
991         assertFalse(conf.isValidating());
992 
993         // Load a non valid XML document. Should work for isValidating() == false
994         conf.load(nonValidFile);
995         assertEquals("customers", conf.getString("table.name"));
996         assertFalse(conf.containsKey("table.fields.field(1).type"));
997 
998         // Now set the validating flag to true
999         conf.setValidating(true);
1000         try
1001         {
1002             conf.load(nonValidFile);
1003             fail("Validation was not performed!");
1004         }
1005         catch(ConfigurationException cex)
1006         {
1007             //ok
1008         }
1009     }
1010 
1011     /**
1012      * Tests handling of empty elements.
1013      */
1014     @Test
1015     public void testEmptyElements() throws ConfigurationException
1016     {
1017         assertTrue(conf.containsKey("empty"));
1018         assertEquals("", conf.getString("empty"));
1019         conf.addProperty("empty2", "");
1020         conf.setProperty("empty", "no more empty");
1021         conf.save(testSaveConf);
1022 
1023         conf = new XMLConfiguration(testSaveConf);
1024         assertEquals("no more empty", conf.getString("empty"));
1025         assertEquals("", conf.getProperty("empty2"));
1026     }
1027 
1028     /**
1029      * Tests the isEmpty() method for an empty configuration that was reloaded.
1030      */
1031     @Test
1032     public void testEmptyReload() throws ConfigurationException
1033     {
1034         XMLConfiguration config = new XMLConfiguration();
1035         assertTrue("Newly created configuration not empty", config.isEmpty());
1036         config.save(testSaveConf);
1037         config.load(testSaveConf);
1038         assertTrue("Reloaded configuration not empty", config.isEmpty());
1039     }
1040 
1041     /**
1042      * Tests whether the encoding is correctly detected by the XML parser. This
1043      * is done by loading an XML file with the encoding "UTF-16". If this
1044      * encoding is not detected correctly, an exception will be thrown that
1045      * "Content is not allowed in prolog". This test case is related to issue
1046      * 34204.
1047      */
1048     @Test
1049     public void testLoadWithEncoding() throws ConfigurationException
1050     {
1051         File file = ConfigurationAssert.getTestFile("testEncoding.xml");
1052         conf = new XMLConfiguration();
1053         conf.load(file);
1054         assertEquals("test3_yoge", conf.getString("yoge"));
1055     }
1056 
1057     /**
1058      * Tests whether the encoding is written to the generated XML file.
1059      */
1060     @Test
1061     public void testSaveWithEncoding() throws ConfigurationException
1062     {
1063         conf = new XMLConfiguration();
1064         conf.setProperty("test", "a value");
1065         conf.setEncoding(ENCODING);
1066 
1067         StringWriter out = new StringWriter();
1068         conf.save(out);
1069         assertTrue("Encoding was not written to file", out.toString().indexOf(
1070                 "encoding=\"" + ENCODING + "\"") >= 0);
1071     }
1072 
1073     /**
1074      * Tests whether a default encoding is used if no specific encoding is set.
1075      * According to the XSLT specification (http://www.w3.org/TR/xslt#output)
1076      * this should be either UTF-8 or UTF-16.
1077      */
1078     @Test
1079     public void testSaveWithNullEncoding() throws ConfigurationException
1080     {
1081         conf = new XMLConfiguration();
1082         conf.setProperty("testNoEncoding", "yes");
1083         conf.setEncoding(null);
1084 
1085         StringWriter out = new StringWriter();
1086         conf.save(out);
1087         assertTrue("Encoding was written to file", out.toString().indexOf(
1088                 "encoding=\"UTF-") >= 0);
1089     }
1090 
1091     /**
1092      * Tests whether the DOCTYPE survives a save operation.
1093      */
1094     @Test
1095     public void testSaveWithDoctype() throws ConfigurationException
1096     {
1097         String content = "<?xml  version=\"1.0\"?>"
1098                 + DOCTYPE
1099                 + "properties"
1100                 + DOCTYPE_DECL
1101                 + "<properties version=\"1.0\"><entry key=\"test\">value</entry></properties>";
1102         StringReader in = new StringReader(content);
1103         conf = new XMLConfiguration();
1104         conf.setFileName("testDtd.xml");
1105         conf.load();
1106         conf.clear();
1107         conf.load(in);
1108 
1109         assertEquals("Wrong public ID", PUBLIC_ID, conf.getPublicID());
1110         assertEquals("Wrong system ID", SYSTEM_ID, conf.getSystemID());
1111         StringWriter out = new StringWriter();
1112         conf.save(out);
1113         assertTrue("Did not find DOCTYPE", out.toString().indexOf(DOCTYPE) >= 0);
1114     }
1115 
1116     /**
1117      * Tests setting public and system IDs for the D'OCTYPE and then saving the
1118      * configuration. This should generate a DOCTYPE declaration.
1119      */
1120     @Test
1121     public void testSaveWithDoctypeIDs() throws ConfigurationException
1122     {
1123         assertNull("A public ID was found", conf.getPublicID());
1124         assertNull("A system ID was found", conf.getSystemID());
1125         conf.setPublicID(PUBLIC_ID);
1126         conf.setSystemID(SYSTEM_ID);
1127         StringWriter out = new StringWriter();
1128         conf.save(out);
1129         assertTrue("Did not find DOCTYPE", out.toString().indexOf(
1130                 DOCTYPE + "testconfig" + DOCTYPE_DECL) >= 0);
1131     }
1132 
1133     /**
1134      * Tests saving a configuration when an invalid transformer factory is
1135      * specified. In this case the error thrown by the TransformerFactory class
1136      * should be caught and re-thrown as a ConfigurationException.
1137      */
1138     @Test
1139     public void testSaveWithInvalidTransformerFactory()
1140     {
1141         System.setProperty(PROP_FACTORY, "an.invalid.Class");
1142         try
1143         {
1144             conf.save(testSaveConf);
1145             fail("Could save with invalid TransformerFactory!");
1146         }
1147         catch (ConfigurationException cex)
1148         {
1149             // ok
1150         }
1151         finally
1152         {
1153             System.getProperties().remove(PROP_FACTORY);
1154         }
1155     }
1156 
1157     /**
1158      * Tests if reloads are recognized by subset().
1159      */
1160     @Test
1161     public void testSubsetWithReload() throws ConfigurationException
1162     {
1163         XMLConfiguration c = setUpReloadTest();
1164         Configuration sub = c.subset("test");
1165         assertEquals("New value not read", "newValue", sub.getString("entity"));
1166     }
1167 
1168     /**
1169      * Tests if reloads are recognized by configurationAt().
1170      */
1171     @Test
1172     public void testConfigurationAtWithReload() throws ConfigurationException
1173     {
1174         XMLConfiguration c = setUpReloadTest();
1175         HierarchicalConfiguration sub = c.configurationAt("test(0)");
1176         assertEquals("New value not read", "newValue", sub.getString("entity"));
1177     }
1178 
1179     /**
1180      * Tests if reloads are recognized by configurationsAt().
1181      */
1182     @Test
1183     public void testConfigurationsAtWithReload() throws ConfigurationException
1184     {
1185         XMLConfiguration c = setUpReloadTest();
1186         List<HierarchicalConfiguration> configs = c.configurationsAt("test");
1187         assertEquals("New value not read", "newValue",
1188                 configs.get(0).getString("entity"));
1189     }
1190 
1191     /**
1192      * Tests whether reloads are recognized when querying the configuration's
1193      * keys.
1194      */
1195     @Test
1196     public void testGetKeysWithReload() throws ConfigurationException
1197     {
1198         XMLConfiguration c = setUpReloadTest();
1199         conf.addProperty("aNewKey", "aNewValue");
1200         conf.save(testSaveConf);
1201         boolean found = false;
1202         for (Iterator<String> it = c.getKeys(); it.hasNext();)
1203         {
1204             if ("aNewKey".equals(it.next()))
1205             {
1206                 found = true;
1207             }
1208         }
1209         assertTrue("Reload not performed", found);
1210     }
1211 
1212     /**
1213      * Tests accessing properties when the XPATH expression engine is set.
1214      */
1215     @Test
1216     public void testXPathExpressionEngine()
1217     {
1218         conf.setExpressionEngine(new XPathExpressionEngine());
1219         assertEquals("Wrong attribute value", "foo\"bar", conf
1220                 .getString("test[1]/entity/@name"));
1221         conf.clear();
1222         assertNull(conf.getString("test[1]/entity/@name"));
1223     }
1224 
1225     /**
1226      * Tests the copy constructor.
1227      */
1228     @Test
1229     public void testInitCopy() throws ConfigurationException
1230     {
1231         XMLConfiguration copy = new XMLConfiguration(conf);
1232         assertEquals("value", copy.getProperty("element"));
1233         assertNull("Document was copied, too", copy.getDocument());
1234         ConfigurationNode root = copy.getRootNode();
1235         for (ConfigurationNode node : root.getChildren())
1236         {
1237             assertNull("Reference was not cleared", node.getReference());
1238         }
1239 
1240         removeTestFile();
1241         copy.setFile(testSaveConf);
1242         copy.save();
1243         copy.clear();
1244         checkSavedConfig(copy);
1245     }
1246 
1247     /**
1248      * Tests setting text of the root element.
1249      */
1250     @Test
1251     public void testSetTextRootElement() throws ConfigurationException
1252     {
1253         conf.setProperty("", "Root text");
1254         conf.save(testSaveConf);
1255         XMLConfiguration copy = new XMLConfiguration();
1256         copy.setFile(testSaveConf);
1257         checkSavedConfig(copy);
1258     }
1259 
1260     /**
1261      * Tests removing the text of the root element.
1262      */
1263     @Test
1264     public void testClearTextRootElement() throws ConfigurationException
1265     {
1266         final String xml = "<e a=\"v\">text</e>";
1267         conf.clear();
1268         StringReader in = new StringReader(xml);
1269         conf.load(in);
1270         assertEquals("Wrong text of root", "text", conf.getString(""));
1271 
1272         conf.clearProperty("");
1273         conf.save(testSaveConf);
1274         XMLConfiguration copy = new XMLConfiguration();
1275         copy.setFile(testSaveConf);
1276         checkSavedConfig(copy);
1277     }
1278 
1279     /**
1280      * Tests list nodes with multiple values and attributes.
1281      */
1282     @Test
1283     public void testListWithAttributes()
1284     {
1285         assertEquals("Wrong number of <a> elements", 6, conf.getList(
1286                 "attrList.a").size());
1287         assertEquals("Wrong value of first element", "ABC", conf
1288                 .getString("attrList.a(0)"));
1289         assertEquals("Wrong value of first name attribute", "x", conf
1290                 .getString("attrList.a(0)[@name]"));
1291         assertEquals("Wrong number of name attributes", 5, conf.getList(
1292                 "attrList.a[@name]").size());
1293     }
1294 
1295     /**
1296      * Tests a list node with attributes that has multiple values separated by
1297      * the list delimiter. In this scenario the attribute should be added to the
1298      * node with the first value.
1299      */
1300     @Test
1301     public void testListWithAttributesMultiValue()
1302     {
1303         assertEquals("Wrong value of 2nd element", "1", conf
1304                 .getString("attrList.a(1)"));
1305         assertEquals("Wrong value of 2nd name attribute", "y", conf
1306                 .getString("attrList.a(1)[@name]"));
1307         for (int i = 2; i <= 3; i++)
1308         {
1309             assertEquals("Wrong value of element " + (i + 1), i, conf
1310                     .getInt("attrList.a(" + i + ")"));
1311             assertFalse("element " + (i + 1) + " has attribute", conf
1312                     .containsKey("attrList.a(2)[@name]"));
1313         }
1314     }
1315 
1316     /**
1317      * Tests a list node with a multi-value attribute and multiple values. All
1318      * attribute values should be assigned to the node with the first value.
1319      */
1320     @Test
1321     public void testListWithMultiAttributesMultiValue()
1322     {
1323         for (int i = 1; i <= 2; i++)
1324         {
1325             assertEquals("Wrong value of multi-valued node", "value" + i, conf
1326                     .getString("attrList.a(" + (i + 3) + ")"));
1327         }
1328         List<Object> attrs = conf.getList("attrList.a(4)[@name]");
1329         final String attrVal = "uvw";
1330         assertEquals("Wrong number of name attributes", attrVal.length(), attrs
1331                 .size());
1332         for (int i = 0; i < attrVal.length(); i++)
1333         {
1334             assertEquals("Wrong value for attribute " + i, String
1335                     .valueOf(attrVal.charAt(i)), attrs.get(i));
1336         }
1337         assertEquals("Wrong value of test attribute", "yes", conf
1338                 .getString("attrList.a(4)[@test]"));
1339         assertFalse("Name attribute for 2nd value", conf
1340                 .containsKey("attrList.a(5)[@name]"));
1341         assertFalse("Test attribute for 2nd value", conf
1342                 .containsKey("attrList.a(5)[@test]"));
1343     }
1344 
1345     /**
1346      * Tests whether the auto save mechanism is triggered by changes at a
1347      * subnode configuration.
1348      */
1349     @Test
1350     public void testAutoSaveWithSubnodeConfig() throws ConfigurationException
1351     {
1352         final String newValue = "I am autosaved";
1353         conf.setFile(testSaveConf);
1354         conf.setAutoSave(true);
1355         Configuration sub = conf.configurationAt("element2.subelement");
1356         sub.setProperty("subsubelement", newValue);
1357         assertEquals("Change not visible to parent", newValue, conf
1358                 .getString("element2.subelement.subsubelement"));
1359         XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
1360         assertEquals("Change was not saved", newValue, conf2
1361                 .getString("element2.subelement.subsubelement"));
1362     }
1363 
1364     /**
1365      * Tests whether a subnode configuration created from another subnode
1366      * configuration of a XMLConfiguration can trigger the auto save mechanism.
1367      */
1368     @Test
1369     public void testAutoSaveWithSubSubnodeConfig() throws ConfigurationException
1370     {
1371         final String newValue = "I am autosaved";
1372         conf.setFile(testSaveConf);
1373         conf.setAutoSave(true);
1374         SubnodeConfiguration sub1 = conf.configurationAt("element2");
1375         SubnodeConfiguration sub2 = sub1.configurationAt("subelement");
1376         sub2.setProperty("subsubelement", newValue);
1377         assertEquals("Change not visible to parent", newValue, conf
1378                 .getString("element2.subelement.subsubelement"));
1379         XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
1380         assertEquals("Change was not saved", newValue, conf2
1381                 .getString("element2.subelement.subsubelement"));
1382     }
1383 
1384     /**
1385      * Tests saving and loading a configuration when delimiter parsing is
1386      * disabled.
1387      */
1388     @Test
1389     public void testSaveDelimiterParsingDisabled()
1390             throws ConfigurationException
1391     {
1392         checkSaveDelimiterParsingDisabled("list.delimiter.test");
1393     }
1394 
1395     /**
1396      * Tests saving and loading a configuration when delimiter parsing is
1397      * disabled and attributes are involved.
1398      */
1399     @Test
1400     public void testSaveDelimiterParsingDisabledAttrs()
1401             throws ConfigurationException
1402     {
1403         checkSaveDelimiterParsingDisabled("list.delimiter.test[@attr]");
1404     }
1405 
1406     /**
1407      * Helper method for testing saving and loading a configuration when
1408      * delimiter parsing is disabled.
1409      *
1410      * @param key the key to be checked
1411      * @throws ConfigurationException if an error occurs
1412      */
1413     private void checkSaveDelimiterParsingDisabled(String key)
1414             throws ConfigurationException
1415     {
1416         conf.clear();
1417         conf.setDelimiterParsingDisabled(true);
1418         conf.load();
1419         conf.setProperty(key, "C:\\Temp\\,C:\\Data\\");
1420         conf.addProperty(key, "a,b,c");
1421         conf.save(testSaveConf);
1422         XMLConfiguration checkConf = new XMLConfiguration();
1423         checkConf.setDelimiterParsingDisabled(true);
1424         checkConf.setFile(testSaveConf);
1425         checkSavedConfig(checkConf);
1426     }
1427 
1428     /**
1429      * Tests multiple attribute values in delimiter parsing disabled mode.
1430      */
1431     @Test
1432     public void testDelimiterParsingDisabledMultiAttrValues() throws ConfigurationException
1433     {
1434         conf.clear();
1435         conf.setDelimiterParsingDisabled(true);
1436         conf.load();
1437         List<Object> expr = conf.getList("expressions[@value]");
1438         assertEquals("Wrong list size", 2, expr.size());
1439         assertEquals("Wrong element 1", "a || (b && c)", expr.get(0));
1440         assertEquals("Wrong element 2", "!d", expr.get(1));
1441     }
1442 
1443     /**
1444      * Tests using multiple attribute values, which are partly escaped when
1445      * delimiter parsing is not disabled.
1446      */
1447     @Test
1448     public void testMultipleAttrValuesEscaped() throws ConfigurationException
1449     {
1450         conf.addProperty("test.dir[@name]", "C:\\Temp\\");
1451         conf.addProperty("test.dir[@name]", "C:\\Data\\");
1452         conf.save(testSaveConf);
1453         XMLConfiguration checkConf = new XMLConfiguration();
1454         checkConf.setFile(testSaveConf);
1455         checkSavedConfig(checkConf);
1456     }
1457 
1458     /**
1459      * Tests a combination of auto save = true and an associated reloading
1460      * strategy.
1461      */
1462     @Test
1463     public void testAutoSaveWithReloadingStrategy() throws ConfigurationException
1464     {
1465         conf.setFile(testSaveConf);
1466         conf.save();
1467         conf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
1468         conf.setAutoSave(true);
1469         assertEquals("Value not found", "value", conf.getProperty("element"));
1470     }
1471 
1472     /**
1473      * Tests adding nodes from another configuration.
1474      */
1475     @Test
1476     public void testAddNodesCopy() throws ConfigurationException
1477     {
1478         XMLConfiguration c2 = new XMLConfiguration(testProperties2);
1479         conf.addNodes("copiedProperties", c2.getRootNode().getChildren());
1480         conf.save(testSaveConf);
1481         XMLConfiguration checkConf = new XMLConfiguration();
1482         checkConf.setFile(testSaveConf);
1483         checkSavedConfig(checkConf);
1484     }
1485 
1486     /**
1487      * Tests whether the addNodes() method triggers an auto save.
1488      */
1489     @Test
1490     public void testAutoSaveAddNodes() throws ConfigurationException
1491     {
1492         conf.setFile(testSaveConf);
1493         conf.setAutoSave(true);
1494         HierarchicalConfiguration.Node node = new HierarchicalConfiguration.Node(
1495                 "addNodesTest", Boolean.TRUE);
1496         Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>(1);
1497         nodes.add(node);
1498         conf.addNodes("test.autosave", nodes);
1499         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
1500         assertTrue("Added nodes are not saved", c2
1501                 .getBoolean("test.autosave.addNodesTest"));
1502     }
1503 
1504     /**
1505      * Tests saving a configuration after a node was added. Test for
1506      * CONFIGURATION-294.
1507      */
1508     @Test
1509     public void testAddNodesAndSave() throws ConfigurationException
1510     {
1511         ConfigurationNode node = new HierarchicalConfiguration.Node("test");
1512         ConfigurationNode child = new HierarchicalConfiguration.Node("child");
1513         node.addChild(child);
1514         ConfigurationNode attr = new HierarchicalConfiguration.Node("attr");
1515         node.addAttribute(attr);
1516         ConfigurationNode node2 = conf.createNode("test2");
1517         Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>(2);
1518         nodes.add(node);
1519         nodes.add(node2);
1520         conf.addNodes("add.nodes", nodes);
1521         conf.setFile(testSaveConf);
1522         conf.save();
1523         conf.setProperty("add.nodes.test", "true");
1524         conf.setProperty("add.nodes.test.child", "yes");
1525         conf.setProperty("add.nodes.test[@attr]", "existing");
1526         conf.setProperty("add.nodes.test2", "anotherValue");
1527         conf.save();
1528         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
1529         assertEquals("Value was not saved", "true", c2
1530                 .getString("add.nodes.test"));
1531         assertEquals("Child value was not saved", "yes", c2
1532                 .getString("add.nodes.test.child"));
1533         assertEquals("Attr value was not saved", "existing", c2
1534                 .getString("add.nodes.test[@attr]"));
1535         assertEquals("Node2 not saved", "anotherValue", c2
1536                 .getString("add.nodes.test2"));
1537     }
1538 
1539     /**
1540      * Tests registering the publicId of a DTD.
1541      */
1542     @Test
1543     public void testRegisterEntityId() throws Exception
1544     {
1545         URL dtdURL = getClass().getResource("/properties.dtd");
1546         final String publicId = "http://commons.apache.org/test/properties.dtd";
1547         conf = new XMLConfiguration("testDtd.xml");
1548         conf.setPublicID(publicId);
1549         conf.save(testSaveConf);
1550         XMLConfiguration checkConfig = new XMLConfiguration();
1551         checkConfig.setFile(testSaveConf);
1552         checkConfig.registerEntityId(publicId, dtdURL);
1553         checkConfig.setValidating(true);
1554         checkSavedConfig(checkConfig);
1555     }
1556 
1557     /**
1558      * Tries to register a null public ID. This should cause an exception.
1559      */
1560     @Test(expected = IllegalArgumentException.class)
1561     public void testRegisterEntityIdNull() throws IOException
1562     {
1563         conf.registerEntityId(null, new URL("http://commons.apache.org"));
1564     }
1565 
1566     /**
1567      * Tests saving a configuration that was created from a hierarchical
1568      * configuration. This test exposes bug CONFIGURATION-301.
1569      */
1570     @Test
1571     public void testSaveAfterCreateWithCopyConstructor()
1572             throws ConfigurationException
1573     {
1574         HierarchicalConfiguration hc = conf.configurationAt("element2");
1575         conf = new XMLConfiguration(hc);
1576         conf.save(testSaveConf);
1577         XMLConfiguration checkConfig = new XMLConfiguration();
1578         checkConfig.setFile(testSaveConf);
1579         checkSavedConfig(checkConfig);
1580         assertEquals("Wrong name of root element", "element2", checkConfig
1581                 .getRootElementName());
1582     }
1583 
1584     /**
1585      * Tests whether the name of the root element is copied when a configuration
1586      * is created using the copy constructor.
1587      */
1588     @Test
1589     public void testCopyRootName() throws ConfigurationException
1590     {
1591         final String rootName = "rootElement";
1592         final String xml = "<" + rootName + "><test>true</test></" + rootName
1593                 + ">";
1594         conf.clear();
1595         conf.load(new StringReader(xml));
1596         XMLConfiguration copy = new XMLConfiguration(conf);
1597         assertEquals("Wrong name of root element", rootName, copy
1598                 .getRootElementName());
1599         copy.save(testSaveConf);
1600         copy = new XMLConfiguration(testSaveConf);
1601         assertEquals("Wrong name of root element after save", rootName, copy
1602                 .getRootElementName());
1603     }
1604 
1605     /**
1606      * Tests whether the name of the root element is copied for a configuration
1607      * for which not yet a document exists.
1608      */
1609     @Test
1610     public void testCopyRootNameNoDocument() throws ConfigurationException
1611     {
1612         final String rootName = "rootElement";
1613         conf = new XMLConfiguration();
1614         conf.setRootElementName(rootName);
1615         conf.setProperty("test", Boolean.TRUE);
1616         XMLConfiguration copy = new XMLConfiguration(conf);
1617         assertEquals("Wrong name of root element", rootName, copy
1618                 .getRootElementName());
1619         copy.save(testSaveConf);
1620         copy = new XMLConfiguration(testSaveConf);
1621         assertEquals("Wrong name of root element after save", rootName, copy
1622                 .getRootElementName());
1623     }
1624 
1625     /**
1626      * Tests adding an attribute node using the addNodes() method.
1627      */
1628     @Test
1629     public void testAddNodesAttributeNode()
1630     {
1631         conf.addProperty("testAddNodes.property[@name]", "prop1");
1632         conf.addProperty("testAddNodes.property(0).value", "value1");
1633         conf.addProperty("testAddNodes.property(-1)[@name]", "prop2");
1634         conf.addProperty("testAddNodes.property(1).value", "value2");
1635         Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
1636         nodes.add(new HierarchicalConfiguration.Node("property"));
1637         conf.addNodes("testAddNodes", nodes);
1638         nodes.clear();
1639         ConfigurationNode nd = new HierarchicalConfiguration.Node("name",
1640                 "prop3");
1641         nd.setAttribute(true);
1642         nodes.add(nd);
1643         conf.addNodes("testAddNodes.property(2)", nodes);
1644         assertEquals("Attribute not added", "prop3", conf
1645                 .getString("testAddNodes.property(2)[@name]"));
1646     }
1647 
1648     /**
1649      * Tests whether spaces are preserved when the xml:space attribute is set.
1650      */
1651     @Test
1652     public void testPreserveSpace()
1653     {
1654         assertEquals("Wrong value of blanc", " ", conf.getString("space.blanc"));
1655         assertEquals("Wrong value of stars", " * * ", conf
1656                 .getString("space.stars"));
1657     }
1658 
1659     /**
1660      * Tests whether the xml:space attribute can be overridden in nested
1661      * elements.
1662      */
1663     @Test
1664     public void testPreserveSpaceOverride()
1665     {
1666         assertEquals("Not trimmed", "Some text", conf
1667                 .getString("space.description"));
1668     }
1669 
1670     /**
1671      * Tests an xml:space attribute with an invalid value. This will be
1672      * interpreted as default.
1673      */
1674     @Test
1675     public void testPreserveSpaceInvalid()
1676     {
1677         assertEquals("Invalid not trimmed", "Some other text", conf
1678                 .getString("space.testInvalid"));
1679     }
1680 
1681     /**
1682      * Tests whether attribute splitting can be disabled.
1683      */
1684     @Test
1685     public void testAttributeSplittingDisabled() throws ConfigurationException
1686     {
1687         List<Object> values = conf.getList("expressions[@value2]");
1688         assertEquals("Wrong number of attribute values", 2, values.size());
1689         assertEquals("Wrong value 1", "a", values.get(0));
1690         assertEquals("Wrong value 2", "b|c", values.get(1));
1691         XMLConfiguration conf2 = new XMLConfiguration();
1692         conf2.setAttributeSplittingDisabled(true);
1693         conf2.setFile(conf.getFile());
1694         conf2.load();
1695         assertEquals("Attribute was split", "a,b|c", conf2
1696                 .getString("expressions[@value2]"));
1697     }
1698 
1699     /**
1700      * Tests disabling both delimiter parsing and attribute splitting.
1701      */
1702     @Test
1703     public void testAttributeSplittingAndDelimiterParsingDisabled()
1704             throws ConfigurationException
1705     {
1706         conf.clear();
1707         conf.setDelimiterParsingDisabled(true);
1708         conf.load();
1709         List<Object> values = conf.getList("expressions[@value2]");
1710         assertEquals("Wrong number of attribute values", 2, values.size());
1711         assertEquals("Wrong value 1", "a,b", values.get(0));
1712         assertEquals("Wrong value 2", "c", values.get(1));
1713         XMLConfiguration conf2 = new XMLConfiguration();
1714         conf2.setAttributeSplittingDisabled(true);
1715         conf2.setDelimiterParsingDisabled(true);
1716         conf2.setFile(conf.getFile());
1717         conf2.load();
1718         assertEquals("Attribute was split", "a,b|c", conf2
1719                 .getString("expressions[@value2]"));
1720     }
1721 
1722     /**
1723      * Tests modifying an XML document and saving it with schema validation enabled.
1724      */
1725     @Test
1726     public void testSaveWithValidation() throws Exception
1727     {
1728         CatalogResolver resolver = new CatalogResolver();
1729         resolver.setCatalogFiles(CATALOG_FILES);
1730         conf = new XMLConfiguration();
1731         conf.setEntityResolver(resolver);
1732         conf.setFileName(testFile2);
1733         conf.setSchemaValidation(true);
1734         conf.load();
1735         conf.setProperty("Employee.SSN", "123456789");
1736         conf.validate();
1737         conf.save(testSaveConf);
1738         conf = new XMLConfiguration(testSaveConf);
1739         assertEquals("123456789", conf.getString("Employee.SSN"));
1740     }
1741 
1742     /**
1743      * Tests modifying an XML document and validating it against the schema.
1744      */
1745     @Test
1746     public void testSaveWithValidationFailure() throws Exception
1747     {
1748         CatalogResolver resolver = new CatalogResolver();
1749         resolver.setCatalogFiles(CATALOG_FILES);
1750         conf = new XMLConfiguration();
1751         conf.setEntityResolver(resolver);
1752         conf.setFileName(testFile2);
1753         conf.setSchemaValidation(true);
1754         conf.load();
1755         conf.setProperty("Employee.Email", "JohnDoe@apache.org");
1756         try
1757         {
1758             conf.validate();
1759             fail("No validation failure on save");
1760         }
1761         catch (Exception e)
1762         {
1763             Throwable cause = e.getCause();
1764             assertNotNull("No cause for exception on save", cause);
1765             assertTrue("Incorrect exception on save", cause instanceof SAXParseException);
1766         }
1767     }
1768 
1769     @Test
1770     public void testConcurrentGetAndReload() throws Exception
1771     {
1772         //final FileConfiguration config = new PropertiesConfiguration("test.properties");
1773         final FileConfiguration config = new XMLConfiguration("test.xml");
1774         config.setReloadingStrategy(new FileAlwaysReloadingStrategy());
1775 
1776         assertTrue("Property not found", config.getProperty("test.short") != null);
1777 
1778         Thread testThreads[] = new Thread[THREAD_COUNT];
1779 
1780         for (int i = 0; i < testThreads.length; ++i)
1781         {
1782             testThreads[i] = new ReloadThread(config);
1783             testThreads[i].start();
1784         }
1785 
1786         for (int i = 0; i < LOOP_COUNT; i++)
1787         {
1788             assertTrue("Property not found", config.getProperty("test.short") != null);
1789         }
1790 
1791         for (int i = 0; i < testThreads.length; ++i)
1792         {
1793             testThreads[i].join();
1794         }
1795     }
1796 
1797     /**
1798      * Tests whether a windows path can be saved correctly. This test is related
1799      * to CONFIGURATION-428.
1800      */
1801     @Test
1802     public void testSaveWindowsPath() throws ConfigurationException
1803     {
1804         conf.clear();
1805         conf.addProperty("path", "C:\\Temp");
1806         StringWriter writer = new StringWriter();
1807         conf.save(writer);
1808         String content = writer.toString();
1809         assertTrue("Path not found: " + content,
1810                 content.indexOf("<path>C:\\Temp</path>") >= 0);
1811         conf.save(testSaveFile);
1812         XMLConfiguration conf2 = new XMLConfiguration(testSaveFile);
1813         assertEquals("Wrong windows path", "C:\\Temp",
1814                 conf2.getString("path"));
1815     }
1816 
1817     /**
1818      * Tests whether an attribute can be set to an empty string. This test is
1819      * related to CONFIGURATION-446.
1820      */
1821     @Test
1822     public void testEmptyAttribute() throws ConfigurationException
1823     {
1824         String key = "element3[@value]";
1825         conf.setProperty(key, "");
1826         assertTrue("Key not found", conf.containsKey(key));
1827         assertEquals("Wrong value", "", conf.getString(key));
1828         conf.save(testSaveConf);
1829         conf = new XMLConfiguration();
1830         conf.load(testSaveConf);
1831         assertTrue("Key not found after save", conf.containsKey(key));
1832         assertEquals("Wrong value after save", "", conf.getString(key));
1833     }
1834 
1835     /**
1836      * Tests whether it is possible to add nodes to a XMLConfiguration through a
1837      * SubnodeConfiguration and whether these nodes have the correct type. This
1838      * test is related to CONFIGURATION-472.
1839      */
1840     @Test
1841     public void testAddNodesToSubnodeConfiguration() throws Exception
1842     {
1843         SubnodeConfiguration sub = conf.configurationAt("element2");
1844         sub.addProperty("newKey", "newvalue");
1845         ConfigurationNode root = conf.getRootNode();
1846         ConfigurationNode elem = root.getChildren("element2").get(0);
1847         ConfigurationNode newNode = elem.getChildren("newKey").get(0);
1848         assertTrue("Wrong node type: " + newNode,
1849                 newNode instanceof XMLConfiguration.XMLNode);
1850     }
1851 
1852     /**
1853      * Tests whether list properties are set correctly if delimiter
1854      * parsing is disabled. This test is related to CONFIGURATION-495.
1855      */
1856     @Test
1857     public void testSetPropertyListWithDelimiterParsingDisabled()
1858             throws ConfigurationException
1859     {
1860         String prop = "delimiterListProp";
1861         conf.setDelimiterParsingDisabled(true);
1862         List<String> list = Arrays.asList("val", "val2", "val3");
1863         conf.setProperty(prop, list);
1864         conf.setFile(testSaveFile);
1865         conf.save();
1866         conf.clear();
1867         conf.load();
1868         assertEquals("Wrong list property", list, conf.getProperty(prop));
1869     }
1870 
1871     /**
1872      * Tests whether list properties are added correctly if delimiter parsing is
1873      * disabled. This test is related to CONFIGURATION-495.
1874      */
1875     @Test
1876     public void testAddPropertyListWithDelimiterParsingDisabled()
1877             throws ConfigurationException
1878     {
1879         String prop = "delimiterListProp";
1880         conf.setDelimiterParsingDisabled(true);
1881         List<String> list = Arrays.asList("val", "val2", "val3");
1882         conf.addProperty(prop, list);
1883         conf.setFile(testSaveFile);
1884         conf.save();
1885         conf.clear();
1886         conf.load();
1887         assertEquals("Wrong list property", list, conf.getProperty(prop));
1888     }
1889 
1890     /**
1891      * Prepares a configuration object for testing a reload operation.
1892      *
1893      * @return the initialized configuration
1894      * @throws ConfigurationException if an error occurs
1895      */
1896     private XMLConfiguration setUpReloadTest() throws ConfigurationException
1897     {
1898         removeTestFile();
1899         conf.save(testSaveConf);
1900         XMLConfiguration c = new XMLConfiguration(testSaveConf);
1901         c.setReloadingStrategy(new FileAlwaysReloadingStrategy());
1902         conf.setProperty("test(0).entity", "newValue");
1903         conf.save(testSaveConf);
1904         return c;
1905     }
1906 
1907     /**
1908      * Removes the test output file if it exists.
1909      */
1910     private void removeTestFile()
1911     {
1912         if (testSaveConf.exists())
1913         {
1914             assertTrue(testSaveConf.delete());
1915         }
1916     }
1917 
1918     /**
1919      * Helper method for checking if a save operation was successful. Loads a
1920      * saved configuration and then tests against a reference configuration.
1921      * @param checkConfig the configuration to check
1922      * @throws ConfigurationException if an error occurs
1923      */
1924     private void checkSavedConfig(FileConfiguration checkConfig) throws ConfigurationException
1925     {
1926         checkConfig.load();
1927         ConfigurationAssert.assertEquals(conf, checkConfig);
1928     }
1929 
1930     private class ReloadThread extends Thread
1931     {
1932         FileConfiguration config;
1933 
1934         ReloadThread(FileConfiguration config)
1935         {
1936             this.config = config;
1937         }
1938         @Override
1939         public void run()
1940         {
1941             for (int i = 0; i < LOOP_COUNT; i++)
1942             {
1943                 config.reload();
1944             }
1945         }
1946     }
1947 }