1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FileWriter;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.net.URL;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Iterator;
32  import java.util.List;
33  
34  import javax.xml.parsers.DocumentBuilder;
35  import javax.xml.parsers.DocumentBuilderFactory;
36  
37  import junit.framework.TestCase;
38  
39  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
40  import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
41  import org.apache.commons.configuration.tree.ConfigurationNode;
42  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.SAXParseException;
45  import org.xml.sax.helpers.DefaultHandler;
46  
47  /***
48   * test for loading and saving xml properties files
49   *
50   * @version $Id: TestXMLConfiguration.java 578579 2007-09-23 16:20:24Z oheger $
51   */
52  public class TestXMLConfiguration extends TestCase
53  {
54      /*** Constant for the used encoding.*/
55      static final String ENCODING = "ISO-8859-1";
56  
57      /*** Constant for the test system ID.*/
58      static final String SYSTEM_ID = "properties.dtd";
59  
60      /*** Constant for the test public ID.*/
61      static final String PUBLIC_ID = "-//Commons Configuration//DTD Test Configuration 1.3//EN";
62  
63      /*** Constant for the DOCTYPE declaration.*/
64      static final String DOCTYPE_DECL = " PUBLIC \"" + PUBLIC_ID + "\" \"" + SYSTEM_ID + "\">";
65  
66      /*** Constant for the DOCTYPE prefix.*/
67      static final String DOCTYPE = "<!DOCTYPE ";
68  
69      /*** Constant for the transformer factory property.*/
70      static final String PROP_FACTORY = "javax.xml.transform.TransformerFactory";
71  
72      /*** The File that we test with */
73      private String testProperties = new File("conf/test.xml").getAbsolutePath();
74      private String testProperties2 = new File("conf/testDigesterConfigurationInclude1.xml").getAbsolutePath();
75      private String testBasePath = new File("conf").getAbsolutePath();
76      private File testSaveConf = new File("target/testsave.xml");
77  
78      private XMLConfiguration conf;
79  
80      protected void setUp() throws Exception
81      {
82          conf = new XMLConfiguration();
83          conf.setFile(new File(testProperties));
84          conf.load();
85          removeTestFile();
86      }
87  
88      public void testGetProperty()
89      {
90          assertEquals("value", conf.getProperty("element"));
91      }
92  
93      public void testGetCommentedProperty()
94      {
95          assertEquals("", conf.getProperty("test.comment"));
96      }
97  
98      public void testGetPropertyWithXMLEntity()
99      {
100         assertEquals("1<2", conf.getProperty("test.entity"));
101     }
102 
103     public void testClearProperty() throws ConfigurationException, IOException
104     {
105         // test non-existent element
106         String key = "clearly";
107         conf.clearProperty(key);
108         assertNull(key, conf.getProperty(key));
109         assertNull(key, conf.getProperty(key));
110 
111         // test single element
112         conf.load();
113         key = "clear.element";
114         conf.clearProperty(key);
115         assertNull(key, conf.getProperty(key));
116         assertNull(key, conf.getProperty(key));
117 
118         // test single element with attribute
119         conf.load();
120         key = "clear.element2";
121         conf.clearProperty(key);
122         assertNull(key, conf.getProperty(key));
123         assertNull(key, conf.getProperty(key));
124         key = "clear.element2[@id]";
125         assertNotNull(key, conf.getProperty(key));
126         assertNotNull(key, conf.getProperty(key));
127 
128         // test non-text/cdata element
129         conf.load();
130         key = "clear.comment";
131         conf.clearProperty(key);
132         assertNull(key, conf.getProperty(key));
133         assertNull(key, conf.getProperty(key));
134 
135         // test cdata element
136         conf.load();
137         key = "clear.cdata";
138         conf.clearProperty(key);
139         assertNull(key, conf.getProperty(key));
140         assertNull(key, conf.getProperty(key));
141 
142         // test multiple sibling elements
143         conf.load();
144         key = "clear.list.item";
145         conf.clearProperty(key);
146         assertNull(key, conf.getProperty(key));
147         assertNull(key, conf.getProperty(key));
148         key = "clear.list.item[@id]";
149         assertNotNull(key, conf.getProperty(key));
150         assertNotNull(key, conf.getProperty(key));
151 
152         // test multiple, disjoined elements
153         conf.load();
154         key = "list.item";
155         conf.clearProperty(key);
156         assertNull(key, conf.getProperty(key));
157         assertNull(key, conf.getProperty(key));
158     }
159 
160     public void testgetProperty() {
161         // test non-leaf element
162         Object property = conf.getProperty("clear");
163         assertNull(property);
164 
165         // test non-existent element
166         property = conf.getProperty("e");
167         assertNull(property);
168 
169         // test non-existent element
170         property = conf.getProperty("element3[@n]");
171         assertNull(property);
172 
173         // test single element
174         property = conf.getProperty("element");
175         assertNotNull(property);
176         assertTrue(property instanceof String);
177         assertEquals("value", property);
178 
179         // test single attribute
180         property = conf.getProperty("element3[@name]");
181         assertNotNull(property);
182         assertTrue(property instanceof String);
183         assertEquals("foo", property);
184 
185         // test non-text/cdata element
186         property = conf.getProperty("test.comment");
187         assertEquals("", property);
188 
189         // test cdata element
190         property = conf.getProperty("test.cdata");
191         assertNotNull(property);
192         assertTrue(property instanceof String);
193         assertEquals("<cdata value>", property);
194 
195         // test multiple sibling elements
196         property = conf.getProperty("list.sublist.item");
197         assertNotNull(property);
198         assertTrue(property instanceof List);
199         List list = (List)property;
200         assertEquals(2, list.size());
201         assertEquals("five", list.get(0));
202         assertEquals("six", list.get(1));
203 
204         // test multiple, disjoined elements
205         property = conf.getProperty("list.item");
206         assertNotNull(property);
207         assertTrue(property instanceof List);
208         list = (List)property;
209         assertEquals(4, list.size());
210         assertEquals("one", list.get(0));
211         assertEquals("two", list.get(1));
212         assertEquals("three", list.get(2));
213         assertEquals("four", list.get(3));
214 
215         // test multiple, disjoined attributes
216         property = conf.getProperty("list.item[@name]");
217         assertNotNull(property);
218         assertTrue(property instanceof List);
219         list = (List)property;
220         assertEquals(2, list.size());
221         assertEquals("one", list.get(0));
222         assertEquals("three", list.get(1));
223     }
224 
225     public void testGetAttribute()
226     {
227         assertEquals("element3[@name]", "foo", conf.getProperty("element3[@name]"));
228     }
229 
230     public void testClearAttribute() throws Exception
231     {
232         // test non-existent attribute
233         String key = "clear[@id]";
234         conf.clearProperty(key);
235         assertNull(key, conf.getProperty(key));
236         assertNull(key, conf.getProperty(key));
237 
238         // test single attribute
239         conf.load();
240         key = "clear.element2[@id]";
241         conf.clearProperty(key);
242         assertNull(key, conf.getProperty(key));
243         assertNull(key, conf.getProperty(key));
244         key = "clear.element2";
245         assertNotNull(key, conf.getProperty(key));
246         assertNotNull(key, conf.getProperty(key));
247 
248         // test multiple, disjoined attributes
249         conf.load();
250         key = "clear.list.item[@id]";
251         conf.clearProperty(key);
252         assertNull(key, conf.getProperty(key));
253         assertNull(key, conf.getProperty(key));
254         key = "clear.list.item";
255         assertNotNull(key, conf.getProperty(key));
256         assertNotNull(key, conf.getProperty(key));
257     }
258 
259     public void testSetAttribute()
260     {
261         // replace an existing attribute
262         conf.setProperty("element3[@name]", "bar");
263         assertEquals("element3[@name]", "bar", conf.getProperty("element3[@name]"));
264 
265         // set a new attribute
266         conf.setProperty("foo[@bar]", "value");
267         assertEquals("foo[@bar]", "value", conf.getProperty("foo[@bar]"));
268 
269         conf.setProperty("name1","value1");
270         assertEquals("value1",conf.getProperty("name1"));
271     }
272 
273     public void testAddAttribute()
274     {
275         conf.addProperty("element3[@name]", "bar");
276 
277         List list = conf.getList("element3[@name]");
278         assertNotNull("null list", list);
279         assertTrue("'foo' element missing", list.contains("foo"));
280         assertTrue("'bar' element missing", list.contains("bar"));
281         assertEquals("list size", 2, list.size());
282     }
283 
284     public void testAddObjectAttribute()
285     {
286         conf.addProperty("test.boolean[@value]", Boolean.TRUE);
287         assertTrue("test.boolean[@value]", conf.getBoolean("test.boolean[@value]"));
288     }
289 
290     /***
291      * Tests setting an attribute on the root element.
292      */
293     public void testSetRootAttribute() throws ConfigurationException
294     {
295         conf.setProperty("[@test]", "true");
296         assertEquals("Root attribute not set", "true", conf
297                 .getString("[@test]"));
298         conf.save(testSaveConf);
299         XMLConfiguration checkConf = new XMLConfiguration();
300         checkConf.setFile(testSaveConf);
301         checkSavedConfig(checkConf);
302         assertTrue("Attribute not found after save", checkConf
303                 .containsKey("[@test]"));
304         checkConf.setProperty("[@test]", "newValue");
305         checkConf.save();
306         conf = checkConf;
307         checkConf = new XMLConfiguration();
308         checkConf.setFile(testSaveConf);
309         checkSavedConfig(checkConf);
310         assertEquals("Attribute not modified after save", "newValue", checkConf
311                 .getString("[@test]"));
312     }
313 
314     /***
315      * Tests whether the configuration's root node is initialized with a
316      * reference to the corresponding XML element.
317      */
318     public void testGetRootReference()
319     {
320         assertNotNull("Root node has no reference", conf.getRootNode()
321                 .getReference());
322     }
323 
324     public void testAddList()
325     {
326         conf.addProperty("test.array", "value1");
327         conf.addProperty("test.array", "value2");
328 
329         List list = conf.getList("test.array");
330         assertNotNull("null list", list);
331         assertTrue("'value1' element missing", list.contains("value1"));
332         assertTrue("'value2' element missing", list.contains("value2"));
333         assertEquals("list size", 2, list.size());
334     }
335 
336     public void testGetComplexProperty()
337     {
338         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
339     }
340 
341     public void testSettingFileNames()
342     {
343         conf = new XMLConfiguration();
344         conf.setFileName(testProperties);
345         assertEquals(testProperties.toString(), conf.getFileName());
346 
347         conf.setBasePath(testBasePath);
348         conf.setFileName("hello.xml");
349         assertEquals("hello.xml", conf.getFileName());
350         assertEquals(testBasePath.toString(), conf.getBasePath());
351         assertEquals(new File(testBasePath, "hello.xml"), conf.getFile());
352 
353         conf.setBasePath(testBasePath);
354         conf.setFileName("subdir/hello.xml");
355         assertEquals("subdir/hello.xml", conf.getFileName());
356         assertEquals(testBasePath.toString(), conf.getBasePath());
357         assertEquals(new File(testBasePath, "subdir/hello.xml"), conf.getFile());
358     }
359 
360     public void testLoad() throws Exception
361     {
362         conf = new XMLConfiguration();
363         conf.setFileName(testProperties);
364         conf.load();
365 
366         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
367     }
368 
369     public void testLoadWithBasePath() throws Exception
370     {
371         conf = new XMLConfiguration();
372 
373         conf.setFileName("test.xml");
374         conf.setBasePath(testBasePath);
375         conf.load();
376 
377         assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
378     }
379 
380     /***
381      * Tests constructing an XMLConfiguration from a non existing file and
382      * later saving to this file.
383      */
384     public void testLoadAndSaveFromFile() throws Exception
385     {
386         // If the file does not exist, an empty config is created
387         conf = new XMLConfiguration(testSaveConf);
388         assertTrue(conf.isEmpty());
389         conf.addProperty("test", "yes");
390         conf.save();
391 
392         conf = new XMLConfiguration(testSaveConf);
393         assertEquals("yes", conf.getString("test"));
394     }
395 
396     /***
397      * Tests loading a configuration from a URL.
398      */
399     public void testLoadFromURL() throws Exception
400     {
401         URL url = new File(testProperties).toURL();
402         conf = new XMLConfiguration(url);
403         assertEquals("value", conf.getProperty("element"));
404         assertEquals(url, conf.getURL());
405     }
406 
407     /***
408      * Tests loading from a stream.
409      */
410     public void testLoadFromStream() throws Exception
411     {
412         String xml = "<?xml version=\"1.0\"?><config><test>1</test></config>";
413         conf = new XMLConfiguration();
414         conf.load(new ByteArrayInputStream(xml.getBytes()));
415         assertEquals(1, conf.getInt("test"));
416 
417         conf = new XMLConfiguration();
418         conf.load(new ByteArrayInputStream(xml.getBytes()), "UTF8");
419         assertEquals(1, conf.getInt("test"));
420     }
421 
422     /***
423      * Tests loading a non well formed XML from a string.
424      */
425     public void testLoadInvalidXML() throws Exception
426     {
427         String xml = "<?xml version=\"1.0\"?><config><test>1</rest></config>";
428         conf = new XMLConfiguration();
429         try
430         {
431             conf.load(new StringReader(xml));
432             fail("Could load invalid XML!");
433         }
434         catch(ConfigurationException cex)
435         {
436             //ok
437         }
438     }
439 
440     public void testSetProperty() throws Exception
441     {
442         conf.setProperty("element.string", "hello");
443 
444         assertEquals("'element.string'", "hello", conf.getString("element.string"));
445         assertEquals("XML value of element.string", "hello", conf.getProperty("element.string"));
446     }
447 
448     public void testAddProperty()
449     {
450         // add a property to a non initialized xml configuration
451         XMLConfiguration config = new XMLConfiguration();
452         config.addProperty("test.string", "hello");
453 
454         assertEquals("'test.string'", "hello", config.getString("test.string"));
455     }
456 
457     public void testAddObjectProperty()
458     {
459         // add a non string property
460         conf.addProperty("test.boolean", Boolean.TRUE);
461         assertTrue("'test.boolean'", conf.getBoolean("test.boolean"));
462     }
463 
464     public void testSave() throws Exception
465     {
466         // add an array of strings to the configuration
467         conf.addProperty("string", "value1");
468         for (int i = 1; i < 5; i++)
469         {
470             conf.addProperty("test.array", "value" + i);
471         }
472 
473         // add an array of strings in an attribute
474         for (int i = 1; i < 5; i++)
475         {
476            conf.addProperty("test.attribute[@array]", "value" + i);
477         }
478 
479         // add comma delimited lists with escaped delimiters
480         conf.addProperty("split.list5", "a//,b//,c");
481         conf.setProperty("element3", "value//,value1//,value2");
482         conf.setProperty("element3[@name]", "foo//,bar");
483 
484         // save the configuration
485         conf.save(testSaveConf.getAbsolutePath());
486 
487         // read the configuration and compare the properties
488         XMLConfiguration checkConfig = new XMLConfiguration();
489         checkConfig.setFileName(testSaveConf.getAbsolutePath());
490         checkSavedConfig(checkConfig);
491     }
492 
493     /***
494      * Tests saving to a URL.
495      */
496     public void testSaveToURL() throws Exception
497     {
498         conf.save(testSaveConf.toURL());
499         XMLConfiguration checkConfig = new XMLConfiguration();
500         checkConfig.setFile(testSaveConf);
501         checkSavedConfig(checkConfig);
502     }
503 
504     /***
505      * Tests saving to a stream.
506      */
507     public void testSaveToStream() throws Exception
508     {
509         assertNull(conf.getEncoding());
510         conf.setEncoding("UTF8");
511         FileOutputStream out = null;
512         try
513         {
514             out = new FileOutputStream(testSaveConf);
515             conf.save(out);
516         }
517         finally
518         {
519             if(out != null)
520             {
521                 out.close();
522             }
523         }
524 
525         XMLConfiguration checkConfig = new XMLConfiguration();
526         checkConfig.setFile(testSaveConf);
527         checkSavedConfig(checkConfig);
528 
529         try
530         {
531             out = new FileOutputStream(testSaveConf);
532             conf.save(out, "UTF8");
533         }
534         finally
535         {
536             if(out != null)
537             {
538                 out.close();
539             }
540         }
541 
542         checkConfig.clear();
543         checkSavedConfig(checkConfig);
544     }
545 
546     public void testAutoSave() throws Exception
547     {
548         conf.setFile(testSaveConf);
549         assertFalse(conf.isAutoSave());
550         conf.setAutoSave(true);
551         assertTrue(conf.isAutoSave());
552         conf.setProperty("autosave", "ok");
553 
554         // reload the configuration
555         XMLConfiguration conf2 = new XMLConfiguration(conf.getFile());
556         assertEquals("'autosave' property", "ok", conf2.getString("autosave"));
557 
558         conf.clearTree("clear");
559         conf2 = new XMLConfiguration(conf.getFile());
560         Configuration sub = conf2.subset("clear");
561         assertTrue(sub.isEmpty());
562     }
563 
564     /***
565      * Tests if a second file can be appended to a first.
566      */
567     public void testAppend() throws Exception
568     {
569         conf = new XMLConfiguration();
570         conf.setFileName(testProperties);
571         conf.load();
572         conf.load(testProperties2);
573         assertEquals("value", conf.getString("element"));
574         assertEquals("tasks", conf.getString("table.name"));
575 
576         conf.save(testSaveConf);
577         conf = new XMLConfiguration(testSaveConf);
578         assertEquals("value", conf.getString("element"));
579         assertEquals("tasks", conf.getString("table.name"));
580         assertEquals("application", conf.getString("table[@tableType]"));
581     }
582 
583     /***
584      * Tests saving attributes (related to issue 34442).
585      */
586     public void testSaveAttributes() throws Exception
587     {
588         conf.clear();
589         conf.load();
590         conf.save(testSaveConf);
591         conf = new XMLConfiguration();
592         conf.load(testSaveConf);
593         assertEquals("foo", conf.getString("element3[@name]"));
594     }
595 
596     /***
597      * Tests collaboration between XMLConfiguration and a reloading strategy.
598      */
599     public void testReloading() throws Exception
600     {
601         assertNotNull(conf.getReloadingStrategy());
602         assertTrue(conf.getReloadingStrategy() instanceof InvariantReloadingStrategy);
603         PrintWriter out = null;
604 
605         try
606         {
607             out = new PrintWriter(new FileWriter(testSaveConf));
608             out.println("<?xml version=\"1.0\"?><config><test>1</test></config>");
609             out.close();
610             out = null;
611             conf.setFile(testSaveConf);
612             FileAlwaysReloadingStrategy strategy = new FileAlwaysReloadingStrategy();
613             strategy.setRefreshDelay(100);
614             conf.setReloadingStrategy(strategy);
615             assertEquals(strategy, conf.getReloadingStrategy());
616             assertEquals("Wrong file monitored", testSaveConf.getAbsolutePath(),
617                     strategy.getMonitoredFile().getAbsolutePath());
618             conf.load();
619             assertEquals(1, conf.getInt("test"));
620 
621             out = new PrintWriter(new FileWriter(testSaveConf));
622             out.println("<?xml version=\"1.0\"?><config><test>2</test></config>");
623             out.close();
624             out = null;
625 
626             int value = conf.getInt("test");
627             assertEquals("No reloading performed", 2, value);
628         }
629         finally
630         {
631             if (out != null)
632             {
633                 out.close();
634             }
635         }
636     }
637 
638     /***
639      * Tests access to tag names with delimiter characters.
640      */
641     public void testComplexNames()
642     {
643         assertEquals("Name with dot", conf.getString("complexNames.my..elem"));
644         assertEquals("Another dot", conf.getString("complexNames.my..elem.sub..elem"));
645     }
646 
647     /***
648      * Tests setting a custom document builder.
649      */
650     public void testCustomDocBuilder() throws Exception
651     {
652         // Load an invalid XML file with the default (non validating)
653         // doc builder. This should work...
654         conf = new XMLConfiguration();
655         conf.load(new File("conf/testValidateInvalid.xml"));
656         assertEquals("customers", conf.getString("table.name"));
657         assertFalse(conf.containsKey("table.fields.field(1).type"));
658 
659         // Now create a validating doc builder and set it.
660         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
661         factory.setValidating(true);
662         DocumentBuilder builder = factory.newDocumentBuilder();
663         builder.setErrorHandler(new DefaultHandler() {
664             public void error(SAXParseException ex) throws SAXException
665             {
666                 throw ex;
667             }
668         });
669         conf = new XMLConfiguration();
670         conf.setDocumentBuilder(builder);
671         try
672         {
673             conf.load(new File("conf/testValidateInvalid.xml"));
674             fail("Could load invalid file with validating set to true!");
675         }
676         catch(ConfigurationException ex)
677         {
678             //ok
679         }
680 
681         // Try to load a valid document with a validating builder
682         conf = new XMLConfiguration();
683         conf.setDocumentBuilder(builder);
684         conf.load(new File("conf/testValidateValid.xml"));
685         assertTrue(conf.containsKey("table.fields.field(1).type"));
686     }
687 
688     /***
689      * Tests the clone() method.
690      */
691     public void testClone()
692     {
693         Configuration c = (Configuration) conf.clone();
694         assertTrue(c instanceof XMLConfiguration);
695         XMLConfiguration copy = (XMLConfiguration) c;
696         assertNotNull(conf.getDocument());
697         assertNull(copy.getDocument());
698         assertNotNull(conf.getFileName());
699         assertNull(copy.getFileName());
700 
701         copy.setProperty("element3", "clonedValue");
702         assertEquals("value", conf.getString("element3"));
703         conf.setProperty("element3[@name]", "originalFoo");
704         assertEquals("foo", copy.getString("element3[@name]"));
705     }
706 
707     /***
708      * Tests saving a configuration after cloning to ensure that the clone and
709      * the original are completely detachted.
710      */
711     public void testCloneWithSave() throws ConfigurationException
712     {
713         XMLConfiguration c = (XMLConfiguration) conf.clone();
714         c.addProperty("test.newProperty", Boolean.TRUE);
715         conf.addProperty("test.orgProperty", Boolean.TRUE);
716         c.save(testSaveConf);
717         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
718         assertTrue("New property after clone() was not saved", c2
719                 .getBoolean("test.newProperty"));
720         assertFalse("Property of original config was saved", c2
721                 .containsKey("test.orgProperty"));
722     }
723 
724     /***
725      * Tests the subset() method. There was a bug that calling subset() had
726      * undesired side effects.
727      */
728     public void testSubset() throws ConfigurationException
729     {
730         conf = new XMLConfiguration();
731         conf.load(new File("conf/testHierarchicalXMLConfiguration.xml"));
732         conf.subset("tables.table(0)");
733         conf.save(testSaveConf);
734 
735         conf = new XMLConfiguration(testSaveConf);
736         assertEquals("users", conf.getString("tables.table(0).name"));
737     }
738 
739     /***
740      * Tests string properties with list delimiters and escaped delimiters.
741      */
742     public void testSplitLists()
743     {
744         assertEquals("a", conf.getString("split.list3[@values]"));
745         assertEquals(2, conf.getMaxIndex("split.list3[@values]"));
746         assertEquals("a,b,c", conf.getString("split.list4[@values]"));
747         assertEquals("a", conf.getString("split.list1"));
748         assertEquals(2, conf.getMaxIndex("split.list1"));
749         assertEquals("a,b,c", conf.getString("split.list2"));
750     }
751 
752     /***
753      * Tests string properties with list delimiters when delimiter parsing
754      * is disabled
755      */
756     public void testDelimiterParsingDisabled() throws ConfigurationException {
757         XMLConfiguration conf2 = new XMLConfiguration();
758         conf2.setDelimiterParsingDisabled(true);
759         conf2.setFile(new File(testProperties));
760         conf2.load();
761 
762         assertEquals("a,b,c", conf2.getString("split.list3[@values]"));
763         assertEquals(0, conf2.getMaxIndex("split.list3[@values]"));
764         assertEquals("a//,b//,c", conf2.getString("split.list4[@values]"));
765         assertEquals("a,b,c", conf2.getString("split.list1"));
766         assertEquals(0, conf2.getMaxIndex("split.list1"));
767         assertEquals("a//,b//,c", conf2.getString("split.list2"));
768     }
769 
770     /***
771      * Tests whether a DTD can be accessed.
772      */
773     public void testDtd() throws ConfigurationException
774     {
775         conf = new XMLConfiguration("testDtd.xml");
776         assertEquals("value1", conf.getString("entry(0)"));
777         assertEquals("test2", conf.getString("entry(1)[@key]"));
778     }
779 
780     /***
781      * Tests DTD validation using the setValidating() method.
782      */
783     public void testValidating() throws ConfigurationException
784     {
785         File nonValidFile = new File("conf/testValidateInvalid.xml");
786         conf = new XMLConfiguration();
787         assertFalse(conf.isValidating());
788 
789         // Load a non valid XML document. Should work for isValidating() == false
790         conf.load(nonValidFile);
791         assertEquals("customers", conf.getString("table.name"));
792         assertFalse(conf.containsKey("table.fields.field(1).type"));
793 
794         // Now set the validating flag to true
795         conf.setValidating(true);
796         try
797         {
798             conf.load(nonValidFile);
799             fail("Validation was not performed!");
800         }
801         catch(ConfigurationException cex)
802         {
803             //ok
804         }
805     }
806 
807     /***
808      * Tests handling of empty elements.
809      */
810     public void testEmptyElements() throws ConfigurationException
811     {
812         assertTrue(conf.containsKey("empty"));
813         assertEquals("", conf.getString("empty"));
814         conf.addProperty("empty2", "");
815         conf.setProperty("empty", "no more empty");
816         conf.save(testSaveConf);
817 
818         conf = new XMLConfiguration(testSaveConf);
819         assertEquals("no more empty", conf.getString("empty"));
820         assertEquals("", conf.getProperty("empty2"));
821     }
822 
823     /***
824      * Tests whether the encoding is correctly detected by the XML parser. This
825      * is done by loading an XML file with the encoding "UTF-16". If this
826      * encoding is not detected correctly, an exception will be thrown that
827      * "Content is not allowed in prolog". This test case is related to issue
828      * 34204.
829      */
830     public void testLoadWithEncoding() throws ConfigurationException
831     {
832         File file = new File("conf/testEncoding.xml");
833         conf = new XMLConfiguration();
834         conf.load(file);
835         assertEquals("test3_yoge", conf.getString("yoge"));
836     }
837 
838     /***
839      * Tests whether the encoding is written to the generated XML file.
840      */
841     public void testSaveWithEncoding() throws ConfigurationException
842     {
843         conf = new XMLConfiguration();
844         conf.setProperty("test", "a value");
845         conf.setEncoding(ENCODING);
846 
847         StringWriter out = new StringWriter();
848         conf.save(out);
849         assertTrue("Encoding was not written to file", out.toString().indexOf(
850                 "encoding=\"" + ENCODING + "\"") >= 0);
851     }
852 
853     /***
854      * Tests whether a default encoding is used if no specific encoding is set.
855      * According to the XSLT specification (http://www.w3.org/TR/xslt#output)
856      * this should be either UTF-8 or UTF-16.
857      */
858     public void testSaveWithNullEncoding() throws ConfigurationException
859     {
860         conf = new XMLConfiguration();
861         conf.setProperty("testNoEncoding", "yes");
862         conf.setEncoding(null);
863 
864         StringWriter out = new StringWriter();
865         conf.save(out);
866         assertTrue("Encoding was written to file", out.toString().indexOf(
867                 "encoding=\"UTF-") >= 0);
868     }
869 
870     /***
871      * Tests whether the DOCTYPE survives a save operation.
872      */
873     public void testSaveWithDoctype() throws ConfigurationException
874     {
875         String content = "<?xml  version=\"1.0\"?>"
876                 + DOCTYPE
877                 + "properties"
878                 + DOCTYPE_DECL
879                 + "<properties version=\"1.0\"><entry key=\"test\">value</entry></properties>";
880         StringReader in = new StringReader(content);
881         conf = new XMLConfiguration();
882         conf.setFileName("conf/testDtd.xml");
883         conf.load();
884         conf.clear();
885         conf.load(in);
886 
887         assertEquals("Wrong public ID", PUBLIC_ID, conf.getPublicID());
888         assertEquals("Wrong system ID", SYSTEM_ID, conf.getSystemID());
889         StringWriter out = new StringWriter();
890         conf.save(out);
891         System.out.println(out.toString());
892         assertTrue("Did not find DOCTYPE", out.toString().indexOf(DOCTYPE) >= 0);
893     }
894 
895     /***
896      * Tests setting public and system IDs for the D'OCTYPE and then saving the
897      * configuration. This should generate a DOCTYPE declaration.
898      */
899     public void testSaveWithDoctypeIDs() throws ConfigurationException
900     {
901         assertNull("A public ID was found", conf.getPublicID());
902         assertNull("A system ID was found", conf.getSystemID());
903         conf.setPublicID(PUBLIC_ID);
904         conf.setSystemID(SYSTEM_ID);
905         StringWriter out = new StringWriter();
906         conf.save(out);
907         assertTrue("Did not find DOCTYPE", out.toString().indexOf(
908                 DOCTYPE + "testconfig" + DOCTYPE_DECL) >= 0);
909     }
910 
911     /***
912      * Tests saving a configuration when an invalid transformer factory is
913      * specified. In this case the error thrown by the TransformerFactory class
914      * should be caught and re-thrown as a ConfigurationException.
915      */
916     public void testSaveWithInvalidTransformerFactory()
917     {
918         System.setProperty(PROP_FACTORY, "an.invalid.Class");
919         try
920         {
921             conf.save(testSaveConf);
922             fail("Could save with invalid TransformerFactory!");
923         }
924         catch (ConfigurationException cex)
925         {
926             // ok
927         }
928         finally
929         {
930             System.getProperties().remove(PROP_FACTORY);
931         }
932     }
933 
934     /***
935      * Tests if reloads are recognized by subset().
936      */
937     public void testSubsetWithReload() throws ConfigurationException
938     {
939         XMLConfiguration c = setUpReloadTest();
940         Configuration sub = c.subset("test");
941         assertEquals("New value not read", "newValue", sub.getString("entity"));
942     }
943 
944     /***
945      * Tests if reloads are recognized by configurationAt().
946      */
947     public void testConfigurationAtWithReload() throws ConfigurationException
948     {
949         XMLConfiguration c = setUpReloadTest();
950         HierarchicalConfiguration sub = c.configurationAt("test(0)");
951         assertEquals("New value not read", "newValue", sub.getString("entity"));
952     }
953 
954     /***
955      * Tests if reloads are recognized by configurationsAt().
956      */
957     public void testConfigurationsAtWithReload() throws ConfigurationException
958     {
959         XMLConfiguration c = setUpReloadTest();
960         List configs = c.configurationsAt("test");
961         assertEquals("New value not read", "newValue",
962                 ((HierarchicalConfiguration) configs.get(0))
963                         .getString("entity"));
964     }
965 
966     /***
967      * Tests accessing properties when the XPATH expression engine is set.
968      */
969     public void testXPathExpressionEngine()
970     {
971         conf.setExpressionEngine(new XPathExpressionEngine());
972         assertEquals("Wrong attribute value", "foo\"bar", conf
973                 .getString("test[1]/entity/@name"));
974         conf.clear();
975         assertNull(conf.getString("test[1]/entity/@name"));
976     }
977 
978     /***
979      * Tests the copy constructor.
980      */
981     public void testInitCopy() throws ConfigurationException
982     {
983     	XMLConfiguration copy = new XMLConfiguration(conf);
984         assertEquals("value", copy.getProperty("element"));
985         assertNull("Document was copied, too", copy.getDocument());
986         ConfigurationNode root = copy.getRootNode();
987         for(Iterator it = root.getChildren().iterator(); it.hasNext();)
988         {
989         	ConfigurationNode node = (ConfigurationNode) it.next();
990         	assertNull("Reference was not cleared", node.getReference());
991         }
992 
993         removeTestFile();
994         copy.setFile(testSaveConf);
995         copy.save();
996         copy.clear();
997         checkSavedConfig(copy);
998     }
999 
1000     /***
1001      * Tests list nodes with multiple values and attributes.
1002      */
1003     public void testListWithAttributes()
1004     {
1005         assertEquals("Wrong number of <a> elements", 6, conf.getList(
1006                 "attrList.a").size());
1007         assertEquals("Wrong value of first element", "ABC", conf
1008                 .getString("attrList.a(0)"));
1009         assertEquals("Wrong value of first name attribute", "x", conf
1010                 .getString("attrList.a(0)[@name]"));
1011         assertEquals("Wrong number of name attributes", 5, conf.getList(
1012                 "attrList.a[@name]").size());
1013     }
1014 
1015     /***
1016      * Tests a list node with attributes that has multiple values separated by
1017      * the list delimiter. In this scenario the attribute should be added to the
1018      * node with the first value.
1019      */
1020     public void testListWithAttributesMultiValue()
1021     {
1022         assertEquals("Wrong value of 2nd element", "1", conf
1023                 .getString("attrList.a(1)"));
1024         assertEquals("Wrong value of 2nd name attribute", "y", conf
1025                 .getString("attrList.a(1)[@name]"));
1026         for (int i = 2; i <= 3; i++)
1027         {
1028             assertEquals("Wrong value of element " + (i + 1), i, conf
1029                     .getInt("attrList.a(" + i + ")"));
1030             assertFalse("element " + (i + 1) + " has attribute", conf
1031                     .containsKey("attrList.a(2)[@name]"));
1032         }
1033     }
1034 
1035     /***
1036      * Tests a list node with a multi-value attribute and multiple values. All
1037      * attribute values should be assigned to the node with the first value.
1038      */
1039     public void testListWithMultiAttributesMultiValue()
1040     {
1041         for (int i = 1; i <= 2; i++)
1042         {
1043             assertEquals("Wrong value of multi-valued node", "value" + i, conf
1044                     .getString("attrList.a(" + (i + 3) + ")"));
1045         }
1046         List attrs = conf.getList("attrList.a(4)[@name]");
1047         final String attrVal = "uvw";
1048         assertEquals("Wrong number of name attributes", attrVal.length(), attrs
1049                 .size());
1050         for (int i = 0; i < attrVal.length(); i++)
1051         {
1052             assertEquals("Wrong value for attribute " + i, String
1053                     .valueOf(attrVal.charAt(i)), attrs.get(i));
1054         }
1055         assertEquals("Wrong value of test attribute", "yes", conf
1056                 .getString("attrList.a(4)[@test]"));
1057         assertFalse("Name attribute for 2nd value", conf
1058                 .containsKey("attrList.a(5)[@name]"));
1059         assertFalse("Test attribute for 2nd value", conf
1060                 .containsKey("attrList.a(5)[@test]"));
1061     }
1062 
1063     /***
1064      * Tests whether the auto save mechanism is triggered by changes at a
1065      * subnode configuration.
1066      */
1067     public void testAutoSaveWithSubnodeConfig() throws ConfigurationException
1068     {
1069         final String newValue = "I am autosaved";
1070         conf.setFile(testSaveConf);
1071         conf.setAutoSave(true);
1072         Configuration sub = conf.configurationAt("element2.subelement");
1073         sub.setProperty("subsubelement", newValue);
1074         assertEquals("Change not visible to parent", newValue, conf
1075                 .getString("element2.subelement.subsubelement"));
1076         XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
1077         assertEquals("Change was not saved", newValue, conf2
1078                 .getString("element2.subelement.subsubelement"));
1079     }
1080 
1081     /***
1082      * Tests whether a subnode configuration created from another subnode
1083      * configuration of a XMLConfiguration can trigger the auto save mechanism.
1084      */
1085     public void testAutoSaveWithSubSubnodeConfig() throws ConfigurationException
1086     {
1087         final String newValue = "I am autosaved";
1088         conf.setFile(testSaveConf);
1089         conf.setAutoSave(true);
1090         SubnodeConfiguration sub1 = conf.configurationAt("element2");
1091         SubnodeConfiguration sub2 = sub1.configurationAt("subelement");
1092         sub2.setProperty("subsubelement", newValue);
1093         assertEquals("Change not visible to parent", newValue, conf
1094                 .getString("element2.subelement.subsubelement"));
1095         XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
1096         assertEquals("Change was not saved", newValue, conf2
1097                 .getString("element2.subelement.subsubelement"));
1098     }
1099 
1100     /***
1101      * Tests saving and loading a configuration when delimiter parsing is
1102      * disabled.
1103      */
1104     public void testSaveDelimiterParsingDisabled()
1105             throws ConfigurationException
1106     {
1107         checkSaveDelimiterParsingDisabled("list.delimiter.test");
1108     }
1109 
1110     /***
1111      * Tests saving and loading a configuration when delimiter parsing is
1112      * disabled and attributes are involved.
1113      */
1114     public void testSaveDelimiterParsingDisabledAttrs()
1115             throws ConfigurationException
1116     {
1117         checkSaveDelimiterParsingDisabled("list.delimiter.test[@attr]");
1118     }
1119 
1120     /***
1121      * Helper method for testing saving and loading a configuration when
1122      * delimiter parsing is disabled.
1123      *
1124      * @param key the key to be checked
1125      * @throws ConfigurationException if an error occurs
1126      */
1127     private void checkSaveDelimiterParsingDisabled(String key)
1128             throws ConfigurationException
1129     {
1130         conf.clear();
1131         conf.setDelimiterParsingDisabled(true);
1132         conf.load();
1133         conf.setProperty(key, "C://Temp//,C://Data//");
1134         conf.addProperty(key, "a,b,c");
1135         conf.save(testSaveConf);
1136         XMLConfiguration checkConf = new XMLConfiguration();
1137         checkConf.setDelimiterParsingDisabled(true);
1138         checkConf.setFile(testSaveConf);
1139         checkSavedConfig(checkConf);
1140     }
1141 
1142     /***
1143      * Tests multiple attribute values in delimiter parsing disabled mode.
1144      */
1145     public void testDelimiterParsingDisabledMultiAttrValues() throws ConfigurationException
1146     {
1147         conf.clear();
1148         conf.setDelimiterParsingDisabled(true);
1149         conf.load();
1150         List expr = conf.getList("expressions[@value]");
1151         assertEquals("Wrong list size", 2, expr.size());
1152         assertEquals("Wrong element 1", "a || (b && c)", expr.get(0));
1153         assertEquals("Wrong element 2", "!d", expr.get(1));
1154     }
1155 
1156     /***
1157      * Tests using multiple attribute values, which are partly escaped when
1158      * delimiter parsing is not disabled.
1159      */
1160     public void testMultipleAttrValuesEscaped() throws ConfigurationException
1161     {
1162         conf.addProperty("test.dir[@name]", "C://Temp//");
1163         conf.addProperty("test.dir[@name]", "C://Data//");
1164         conf.save(testSaveConf);
1165         XMLConfiguration checkConf = new XMLConfiguration();
1166         checkConf.setFile(testSaveConf);
1167         checkSavedConfig(checkConf);
1168     }
1169 
1170     /***
1171      * Tests a combination of auto save = true and an associated reloading
1172      * strategy.
1173      */
1174     public void testAutoSaveWithReloadingStrategy() throws ConfigurationException
1175     {
1176         conf.setFile(testSaveConf);
1177         conf.save();
1178         conf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
1179         conf.setAutoSave(true);
1180         assertEquals("Value not found", "value", conf.getProperty("element"));
1181     }
1182 
1183     /***
1184      * Tests adding nodes from another configuration.
1185      */
1186     public void testAddNodesCopy() throws ConfigurationException
1187     {
1188         XMLConfiguration c2 = new XMLConfiguration(testProperties2);
1189         conf.addNodes("copiedProperties", c2.getRootNode().getChildren());
1190         conf.save(testSaveConf);
1191         XMLConfiguration checkConf = new XMLConfiguration();
1192         checkConf.setFile(testSaveConf);
1193         checkSavedConfig(checkConf);
1194     }
1195 
1196     /***
1197      * Tests whether the addNodes() method triggers an auto save.
1198      */
1199     public void testAutoSaveAddNodes() throws ConfigurationException
1200     {
1201         conf.setFile(testSaveConf);
1202         conf.setAutoSave(true);
1203         HierarchicalConfiguration.Node node = new HierarchicalConfiguration.Node(
1204                 "addNodesTest", Boolean.TRUE);
1205         Collection nodes = new ArrayList(1);
1206         nodes.add(node);
1207         conf.addNodes("test.autosave", nodes);
1208         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
1209         assertTrue("Added nodes are not saved", c2
1210                 .getBoolean("test.autosave.addNodesTest"));
1211     }
1212 
1213     /***
1214      * Tests saving a configuration after a node was added. Test for
1215      * CONFIGURATION-294.
1216      */
1217     public void testAddNodesAndSave() throws ConfigurationException
1218     {
1219         ConfigurationNode node = new HierarchicalConfiguration.Node("test");
1220         ConfigurationNode child = new HierarchicalConfiguration.Node("child");
1221         node.addChild(child);
1222         ConfigurationNode attr = new HierarchicalConfiguration.Node("attr");
1223         node.addAttribute(attr);
1224         ConfigurationNode node2 = conf.createNode("test2");
1225         Collection nodes = new ArrayList(2);
1226         nodes.add(node);
1227         nodes.add(node2);
1228         conf.addNodes("add.nodes", nodes);
1229         conf.setFile(testSaveConf);
1230         conf.save();
1231         conf.setProperty("add.nodes.test", "true");
1232         conf.setProperty("add.nodes.test.child", "yes");
1233         conf.setProperty("add.nodes.test[@attr]", "existing");
1234         conf.setProperty("add.nodes.test2", "anotherValue");
1235         conf.save();
1236         XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
1237         assertEquals("Value was not saved", "true", c2
1238                 .getString("add.nodes.test"));
1239         assertEquals("Child value was not saved", "yes", c2
1240                 .getString("add.nodes.test.child"));
1241         assertEquals("Attr value was not saved", "existing", c2
1242                 .getString("add.nodes.test[@attr]"));
1243         assertEquals("Node2 not saved", "anotherValue", c2
1244                 .getString("add.nodes.test2"));
1245     }
1246 
1247     /***
1248      * Tests registering the publicId of a DTD.
1249      */
1250     public void testRegisterEntityId() throws ConfigurationException,
1251             IOException
1252     {
1253         File dtdFile = new File("conf/properties.dtd");
1254         final String publicId = "http://commons.apache.org/test/properties.dtd";
1255         conf = new XMLConfiguration("testDtd.xml");
1256         conf.setPublicID(publicId);
1257         conf.save(testSaveConf);
1258         XMLConfiguration checkConfig = new XMLConfiguration();
1259         checkConfig.setFile(testSaveConf);
1260         checkConfig.registerEntityId(publicId, dtdFile.toURL());
1261         checkConfig.setValidating(true);
1262         checkSavedConfig(checkConfig);
1263     }
1264 
1265     /***
1266      * Tries to register a null public ID. This should cause an exception.
1267      */
1268     public void testRegisterEntityIdNull() throws IOException
1269     {
1270         try
1271         {
1272             conf.registerEntityId(null, new URL("http://commons.apache.org"));
1273             fail("Could register null public ID!");
1274         }
1275         catch (IllegalArgumentException iex)
1276         {
1277             // ok
1278         }
1279     }
1280 
1281     /***
1282      * Prepares a configuration object for testing a reload operation.
1283      *
1284      * @return the initialized configuration
1285      * @throws ConfigurationException if an error occurs
1286      */
1287     private XMLConfiguration setUpReloadTest() throws ConfigurationException
1288     {
1289         removeTestFile();
1290         conf.save(testSaveConf);
1291         XMLConfiguration c = new XMLConfiguration(testSaveConf);
1292         c.setReloadingStrategy(new FileAlwaysReloadingStrategy());
1293         conf.setProperty("test(0).entity", "newValue");
1294         conf.save(testSaveConf);
1295         return c;
1296     }
1297 
1298     /***
1299      * Removes the test output file if it exists.
1300      */
1301     private void removeTestFile()
1302     {
1303         if (testSaveConf.exists())
1304         {
1305             assertTrue(testSaveConf.delete());
1306         }
1307     }
1308 
1309     /***
1310      * Helper method for checking if a save operation was successful. Loads a
1311      * saved configuration and then tests against a reference configuration.
1312      * @param checkConfig the configuration to check
1313      * @throws ConfigurationException if an error occurs
1314      */
1315     private void checkSavedConfig(FileConfiguration checkConfig) throws ConfigurationException
1316     {
1317         checkConfig.load();
1318         ConfigurationAssert.assertEquals(conf, checkConfig);
1319     }
1320 }