001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.configuration;
019    
020    import static org.junit.Assert.assertEquals;
021    import static org.junit.Assert.assertFalse;
022    import static org.junit.Assert.assertNotNull;
023    import static org.junit.Assert.assertNull;
024    import static org.junit.Assert.assertTrue;
025    import static org.junit.Assert.fail;
026    
027    import java.io.ByteArrayInputStream;
028    import java.io.File;
029    import java.io.FileOutputStream;
030    import java.io.FileWriter;
031    import java.io.IOException;
032    import java.io.PrintWriter;
033    import java.io.StringReader;
034    import java.io.StringWriter;
035    import java.net.URL;
036    import java.util.ArrayList;
037    import java.util.Arrays;
038    import java.util.Collection;
039    import java.util.Iterator;
040    import java.util.List;
041    
042    import javax.xml.parsers.DocumentBuilder;
043    import javax.xml.parsers.DocumentBuilderFactory;
044    import javax.xml.parsers.ParserConfigurationException;
045    
046    import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
047    import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
048    import org.apache.commons.configuration.resolver.CatalogResolver;
049    import org.apache.commons.configuration.tree.ConfigurationNode;
050    import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
051    import org.junit.Before;
052    import org.junit.Test;
053    import org.xml.sax.SAXException;
054    import org.xml.sax.SAXParseException;
055    import org.xml.sax.helpers.DefaultHandler;
056    
057    /**
058     * test for loading and saving xml properties files
059     *
060     * @version $Id: TestXMLConfiguration.java 1330666 2012-04-26 06:12:30Z oheger $
061     */
062    public class TestXMLConfiguration
063    {
064        /** XML Catalog */
065        private static final String CATALOG_FILES = ConfigurationAssert
066                .getTestFile("catalog.xml").getAbsolutePath();
067    
068        /** Constant for the used encoding.*/
069        static final String ENCODING = "ISO-8859-1";
070    
071        /** Constant for the test system ID.*/
072        static final String SYSTEM_ID = "properties.dtd";
073    
074        /** Constant for the test public ID.*/
075        static final String PUBLIC_ID = "-//Commons Configuration//DTD Test Configuration 1.3//EN";
076    
077        /** Constant for the DOCTYPE declaration.*/
078        static final String DOCTYPE_DECL = " PUBLIC \"" + PUBLIC_ID + "\" \"" + SYSTEM_ID + "\">";
079    
080        /** Constant for the DOCTYPE prefix.*/
081        static final String DOCTYPE = "<!DOCTYPE ";
082    
083        /** Constant for the transformer factory property.*/
084        static final String PROP_FACTORY = "javax.xml.transform.TransformerFactory";
085    
086        /** The File that we test with */
087        private String testProperties = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
088        private String testProperties2 = ConfigurationAssert.getTestFile("testDigesterConfigurationInclude1.xml").getAbsolutePath();
089        private String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
090        private File testSaveConf = ConfigurationAssert.getOutFile("testsave.xml");
091        private File testSaveFile = ConfigurationAssert.getOutFile("testsample2.xml");
092        private String testFile2 = ConfigurationAssert.getTestFile("sample.xml").getAbsolutePath();
093    
094        /** Constant for the number of test threads. */
095        private static final int THREAD_COUNT = 5;
096    
097        /** Constant for the number of loops in tests with multiple threads. */
098        private static final int LOOP_COUNT = 100;
099    
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    }