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.assertNotSame;
024    import static org.junit.Assert.assertNull;
025    import static org.junit.Assert.assertSame;
026    import static org.junit.Assert.assertTrue;
027    import static org.junit.Assert.fail;
028    
029    import java.io.BufferedReader;
030    import java.io.File;
031    import java.io.FileOutputStream;
032    import java.io.FileReader;
033    import java.io.FileWriter;
034    import java.io.IOException;
035    import java.io.InputStream;
036    import java.io.OutputStream;
037    import java.io.PrintWriter;
038    import java.io.Reader;
039    import java.io.StringReader;
040    import java.io.StringWriter;
041    import java.io.Writer;
042    import java.net.HttpURLConnection;
043    import java.net.URL;
044    import java.net.URLConnection;
045    import java.net.URLStreamHandler;
046    import java.util.ArrayList;
047    import java.util.Arrays;
048    import java.util.HashSet;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Set;
052    
053    import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
054    import org.apache.commons.lang.SystemUtils;
055    import org.junit.Before;
056    import org.junit.Test;
057    
058    /**
059     * Test for loading and saving properties files.
060     *
061     * @version $Id: TestPropertiesConfiguration.java 1330666 2012-04-26 06:12:30Z oheger $
062     */
063    public class TestPropertiesConfiguration
064    {
065        /** Constant for a test property name.*/
066        private static final String PROP_NAME = "testProperty";
067    
068        /** Constant for a test property value.*/
069        private static final String PROP_VALUE = "value";
070    
071        /** The configuration to be tested.*/
072        private PropertiesConfiguration conf;
073    
074        /** The File that we test with */
075        private static String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
076    
077        private static String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
078        private static String testBasePath2 = ConfigurationAssert.TEST_DIR.getParentFile().getAbsolutePath();
079        private static File testSavePropertiesFile = ConfigurationAssert.getOutFile("testsave.properties");
080    
081        @Before
082        public void setUp() throws Exception
083        {
084            conf = new PropertiesConfiguration(testProperties);
085    
086            // remove the test save file if it exists
087            if (testSavePropertiesFile.exists())
088            {
089                assertTrue("Test output file could not be deleted",
090                        testSavePropertiesFile.delete());
091            }
092        }
093    
094        @Test
095        public void testLoad() throws Exception
096        {
097            String loaded = conf.getString("configuration.loaded");
098            assertEquals("true", loaded);
099        }
100    
101        /**
102         * Tests if properties can be appended by simply calling load() another
103         * time.
104         */
105        @Test
106        public void testAppend() throws Exception
107        {
108            File file2 = ConfigurationAssert.getTestFile("threesome.properties");
109            conf.load(file2);
110            assertEquals("aaa", conf.getString("test.threesome.one"));
111            assertEquals("true", conf.getString("configuration.loaded"));
112        }
113    
114        /**
115         * Tests that empty properties are treated as the empty string
116         * (rather than as null).
117         */
118        @Test
119        public void testEmpty() throws Exception
120        {
121            String empty = conf.getString("test.empty");
122            assertNotNull(empty);
123            assertEquals("", empty);
124        }
125    
126        /**
127         * Tests that references to other properties work
128         */
129        @Test
130        public void testReference() throws Exception
131        {
132            assertEquals("baseextra", conf.getString("base.reference"));
133        }
134    
135        /**
136         * test if includes properties get loaded too
137         */
138        @Test
139        public void testLoadInclude() throws Exception
140        {
141            String loaded = conf.getString("include.loaded");
142            assertEquals("true", loaded);
143        }
144    
145        /**
146         * test if includes properties from interpolated file
147         * name get loaded
148         */
149        @Test
150        public void testLoadIncludeInterpol() throws Exception
151        {
152            String loaded = conf.getString("include.interpol.loaded");
153            assertEquals("true", loaded);
154        }
155    
156        @Test
157        public void testSetInclude() throws Exception
158        {
159            // change the include key
160            PropertiesConfiguration.setInclude("import");
161    
162            // load the configuration
163            PropertiesConfiguration conf = new PropertiesConfiguration();
164            conf.load(ConfigurationAssert.getTestFile("test.properties"));
165    
166            // restore the previous value for the other tests
167            PropertiesConfiguration.setInclude("include");
168    
169            assertNull(conf.getString("include.loaded"));
170        }
171    
172        /**
173         * Tests {@code List} parsing.
174         */
175        @Test
176        public void testList() throws Exception
177        {
178            List<Object> packages = conf.getList("packages");
179            // we should get 3 packages here
180            assertEquals(3, packages.size());
181        }
182    
183        @Test
184        public void testSave() throws Exception
185        {
186            // add an array of strings to the configuration
187            conf.addProperty("string", "value1");
188            List<Object> list = new ArrayList<Object>();
189            for (int i = 1; i < 5; i++)
190            {
191                list.add("value" + i);
192            }
193            conf.addProperty("array", list);
194    
195            // save the configuration
196            String filename = testSavePropertiesFile.getAbsolutePath();
197            conf.save(filename);
198    
199            assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
200    
201            // read the configuration and compare the properties
202            PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
203            ConfigurationAssert.assertEquals(conf, checkConfig);
204    
205            // Save it again, verifying a save with a filename works.
206            checkConfig.save();
207        }
208    
209        @Test
210        public void testSaveToCustomURL() throws Exception
211        {
212            // save the configuration to a custom URL
213            URL url = new URL("foo", "", 0, "./target/testsave-custom-url.properties", new FileURLStreamHandler());
214            conf.save(url);
215    
216            // reload the configuration
217            Configuration config2 = new PropertiesConfiguration(url);
218            assertEquals("true", config2.getString("configuration.loaded"));
219        }
220    
221        @Test
222        public void testInMemoryCreatedSave() throws Exception
223        {
224            PropertiesConfiguration pc = new PropertiesConfiguration();
225            // add an array of strings to the configuration
226            pc.addProperty("string", "value1");
227            List<Object> list = new ArrayList<Object>();
228            for (int i = 1; i < 5; i++)
229            {
230                list.add("value" + i);
231            }
232            pc.addProperty("array", list);
233    
234            // save the configuration
235            String filename = testSavePropertiesFile.getAbsolutePath();
236            pc.save(filename);
237    
238            assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
239    
240            // read the configuration and compare the properties
241            PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
242            ConfigurationAssert.assertEquals(pc, checkConfig);
243    
244            // Save it again, verifying a save with a filename works.
245            checkConfig.save();
246        }
247    
248        /**
249         * Tests saving a configuration when delimiter parsing is disabled.
250         */
251        @Test
252        public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException
253        {
254            conf.clear();
255            conf.setDelimiterParsingDisabled(true);
256            conf.addProperty("test.list", "a,b,c");
257            conf.addProperty("test.dirs", "C:\\Temp\\,D:\\Data\\");
258            conf.save(testSavePropertiesFile);
259    
260            PropertiesConfiguration checkConfig = new PropertiesConfiguration();
261            checkConfig.setDelimiterParsingDisabled(true);
262            checkConfig.setFile(testSavePropertiesFile);
263            checkConfig.load();
264            ConfigurationAssert.assertEquals(conf, checkConfig);
265        }
266    
267        @Test(expected = ConfigurationException.class)
268        public void testSaveMissingFilename() throws ConfigurationException
269        {
270            PropertiesConfiguration pc = new PropertiesConfiguration();
271            pc.save();
272        }
273    
274        /**
275         * Tests if the base path is taken into account by the save() method.
276         * @throws Exception if an error occurs
277         */
278        @Test
279        public void testSaveWithBasePath() throws Exception
280        {
281            conf.setProperty("test", "true");
282            conf.setBasePath(testSavePropertiesFile.getParentFile().toURI().toURL()
283                    .toString());
284            conf.setFileName(testSavePropertiesFile.getName());
285            conf.save();
286            assertTrue(testSavePropertiesFile.exists());
287        }
288    
289        /**
290         * Tests whether the escape character for list delimiters can be itself
291         * escaped and survives a save operation.
292         */
293        @Test
294        public void testSaveEscapedEscapingCharacter()
295                throws ConfigurationException
296        {
297            conf.addProperty("test.dirs", "C:\\Temp\\\\,D:\\Data\\\\,E:\\Test\\");
298            List<Object> dirs = conf.getList("test.dirs");
299            assertEquals("Wrong number of list elements", 3, dirs.size());
300            conf.save(testSavePropertiesFile);
301    
302            PropertiesConfiguration checkConfig = new PropertiesConfiguration(
303                    testSavePropertiesFile);
304            ConfigurationAssert.assertEquals(conf, checkConfig);
305        }
306    
307        @Test
308        public void testLoadViaProperty() throws Exception
309        {
310            PropertiesConfiguration pc = new PropertiesConfiguration();
311            pc.setFileName(testProperties);
312            pc.load();
313    
314            assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
315        }
316    
317        @Test
318        public void testLoadViaPropertyWithBasePath() throws Exception
319        {
320            PropertiesConfiguration pc = new PropertiesConfiguration();
321            pc.setBasePath(testBasePath);
322            pc.setFileName("test.properties");
323            pc.load();
324    
325            assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
326        }
327    
328        @Test
329        public void testLoadViaPropertyWithBasePath2() throws Exception
330        {
331            PropertiesConfiguration pc = new PropertiesConfiguration();
332            pc.setBasePath(testBasePath2);
333            pc.setFileName("test.properties");
334            pc.load();
335    
336            assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
337    
338            pc = new PropertiesConfiguration();
339            pc.setBasePath(testBasePath2);
340            pc.setFileName("test.properties");
341            pc.load();
342    
343            assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
344        }
345    
346        @Test
347        public void testLoadFromFile() throws Exception
348        {
349            File file = ConfigurationAssert.getTestFile("test.properties");
350            conf = new PropertiesConfiguration(file);
351    
352            assertEquals("true", conf.getString("configuration.loaded"));
353        }
354    
355        @Test(expected = ConfigurationException.class)
356        public void testLoadUnexistingFile() throws ConfigurationException
357        {
358            conf = new PropertiesConfiguration("Unexisting file");
359        }
360    
361        /**
362         * Tests to load a file with enabled auto save mode.
363         */
364        @Test
365        public void testLoadWithAutoSave() throws Exception
366        {
367            setUpSavedProperties();
368        }
369    
370        /**
371         * Tests the auto save functionality when an existing property is modified.
372         */
373        @Test
374        public void testLoadWithAutoSaveAndSetExisting() throws Exception
375        {
376            setUpSavedProperties();
377            conf.setProperty("a", "moreThanOne");
378            checkSavedConfig();
379        }
380    
381        /**
382         * Tests the auto save functionality when a new property is added using the
383         * setProperty() method.
384         */
385        @Test
386        public void testLoadWithAutoSaveAndSetNew() throws Exception
387        {
388            setUpSavedProperties();
389            conf.setProperty("d", "four");
390            checkSavedConfig();
391        }
392    
393        /**
394         * Tests the auto save functionality when a new property is added using the
395         * addProperty() method.
396         */
397        @Test
398        public void testLoadWithAutoSaveAndAdd() throws Exception
399        {
400            setUpSavedProperties();
401            conf.addProperty("d", "four");
402            checkSavedConfig();
403        }
404    
405        /**
406         * Tests the auto save functionality when a property is removed.
407         */
408        @Test
409        public void testLoadWithAutoSaveAndClear() throws Exception
410        {
411            setUpSavedProperties();
412            conf.clearProperty("c");
413            PropertiesConfiguration checkConfig = checkSavedConfig();
414            assertFalse("The saved configuration contain the key '" + "c" + "'", checkConfig.containsKey("c"));
415        }
416    
417        /**
418         * Creates a properties file on disk. Used for testing load and save
419         * operations.
420         *
421         * @throws IOException if an I/O error occurs
422         */
423        private void setUpSavedProperties() throws IOException, ConfigurationException
424        {
425            PrintWriter out = null;
426    
427            try
428            {
429                out = new PrintWriter(new FileWriter(testSavePropertiesFile));
430                out.println("a = one");
431                out.println("b = two");
432                out.println("c = three");
433                out.close();
434                out = null;
435    
436                conf = new PropertiesConfiguration();
437                conf.setAutoSave(true);
438                conf.setFile(testSavePropertiesFile);
439                conf.load();
440                assertEquals("one", conf.getString("a"));
441                assertEquals("two", conf.getString("b"));
442                assertEquals("three", conf.getString("c"));
443            }
444            finally
445            {
446                if (out != null)
447                {
448                    out.close();
449                }
450            }
451        }
452    
453        /**
454         * Helper method for testing a saved configuration. Reads in the file using
455         * a new instance and compares this instance with the original one.
456         *
457         * @return the newly created configuration instance
458         * @throws ConfigurationException if an error occurs
459         */
460        private PropertiesConfiguration checkSavedConfig()
461                throws ConfigurationException
462        {
463            PropertiesConfiguration checkConfig = new PropertiesConfiguration(testSavePropertiesFile);
464            ConfigurationAssert.assertEquals(conf, checkConfig);
465            return checkConfig;
466        }
467    
468        @Test
469        public void testGetStringWithEscapedChars()
470        {
471            String property = conf.getString("test.unescape");
472            assertEquals("String with escaped characters", "This \n string \t contains \" escaped \\ characters", property);
473        }
474    
475        @Test
476        public void testGetStringWithEscapedComma()
477        {
478            String property = conf.getString("test.unescape.list-separator");
479            assertEquals("String with an escaped list separator", "This string contains , an escaped list separator", property);
480        }
481    
482        @Test
483        public void testUnescapeJava()
484        {
485            assertEquals("test\\,test", PropertiesConfiguration.unescapeJava("test\\,test", ','));
486        }
487    
488        @Test
489        public void testEscapedKey() throws Exception
490        {
491            PropertiesConfiguration conf = new PropertiesConfiguration();
492            conf.load(new StringReader("\\u0066\\u006f\\u006f=bar"));
493    
494            assertEquals("value of the 'foo' property", "bar", conf.getString("foo"));
495        }
496    
497        @Test
498        public void testMixedArray()
499        {
500            String[] array = conf.getStringArray("test.mixed.array");
501    
502            assertEquals("array length", 4, array.length);
503            assertEquals("1st element", "a", array[0]);
504            assertEquals("2nd element", "b", array[1]);
505            assertEquals("3rd element", "c", array[2]);
506            assertEquals("4th element", "d", array[3]);
507        }
508    
509        @Test
510        public void testMultilines()
511        {
512            String property = "This is a value spread out across several adjacent "
513                    + "natural lines by escaping the line terminator with "
514                    + "a backslash character.";
515    
516            assertEquals("'test.multilines' property", property, conf.getString("test.multilines"));
517        }
518    
519        @Test
520        public void testChangingDefaultListDelimiter() throws Exception
521        {
522            PropertiesConfiguration pc = new PropertiesConfiguration(testProperties);
523            assertEquals(4, pc.getList("test.mixed.array").size());
524    
525            char delimiter = PropertiesConfiguration.getDefaultListDelimiter();
526            PropertiesConfiguration.setDefaultListDelimiter('^');
527            pc = new PropertiesConfiguration(testProperties);
528            assertEquals(2, pc.getList("test.mixed.array").size());
529            PropertiesConfiguration.setDefaultListDelimiter(delimiter);
530        }
531    
532        @Test
533        public void testChangingListDelimiter() throws Exception
534        {
535            PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
536            assertEquals(4, pc1.getList("test.mixed.array").size());
537    
538            PropertiesConfiguration pc2 = new PropertiesConfiguration();
539            pc2.setListDelimiter('^');
540            pc2.setFileName(testProperties);
541            pc2.load();
542            assertEquals("Should obtain the first value", "a", pc2.getString("test.mixed.array"));
543            assertEquals(2, pc2.getList("test.mixed.array").size());
544        }
545    
546        @Test
547        public void testDisableListDelimiter() throws Exception
548        {
549            PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
550            assertEquals(4, pc1.getList("test.mixed.array").size());
551    
552            PropertiesConfiguration pc2 = new PropertiesConfiguration();
553            pc2.setDelimiterParsingDisabled(true);
554            pc2.setFileName(testProperties);
555            pc2.load();
556            assertEquals(2, pc2.getList("test.mixed.array").size());
557        }
558    
559        /**
560         * Tests escaping of an end of line with a backslash.
561         */
562        @Test
563        public void testNewLineEscaping()
564        {
565            List<Object> list = conf.getList("test.path");
566            assertEquals(3, list.size());
567            assertEquals("C:\\path1\\", list.get(0));
568            assertEquals("C:\\path2\\", list.get(1));
569            assertEquals("C:\\path3\\complex\\test\\", list.get(2));
570        }
571    
572        /**
573         * Tests if included files are loaded when the source lies in the class path.
574         */
575        @Test
576        public void testLoadIncludeFromClassPath() throws ConfigurationException
577        {
578            conf = new PropertiesConfiguration("test.properties");
579            assertEquals("true", conf.getString("include.loaded"));
580        }
581    
582        /**
583         * Test if the lines starting with # or ! are properly ignored.
584         */
585        @Test
586        public void testComment() {
587            assertFalse("comment line starting with '#' parsed as a property", conf.containsKey("#comment"));
588            assertFalse("comment line starting with '!' parsed as a property", conf.containsKey("!comment"));
589        }
590    
591        /**
592         * Check that key/value separators can be part of a key.
593         */
594        @Test
595        public void testEscapedKeyValueSeparator()
596        {
597            assertEquals("Escaped separator '=' not supported in keys", "foo", conf.getProperty("test.separator=in.key"));
598            assertEquals("Escaped separator ':' not supported in keys", "bar", conf.getProperty("test.separator:in.key"));
599            assertEquals("Escaped separator '\\t' not supported in keys", "foo", conf.getProperty("test.separator\tin.key"));
600            assertEquals("Escaped separator '\\f' not supported in keys", "bar", conf.getProperty("test.separator\fin.key"));
601            assertEquals("Escaped separator ' ' not supported in keys"  , "foo", conf.getProperty("test.separator in.key"));
602        }
603    
604        /**
605         * Test all acceptable key/value separators ('=', ':' or white spaces).
606         */
607        @Test
608        public void testKeyValueSeparators() {
609            assertEquals("equal separator not properly parsed",      "foo", conf.getProperty("test.separator.equal"));
610            assertEquals("colon separator not properly parsed",      "foo", conf.getProperty("test.separator.colon"));
611            assertEquals("tab separator not properly parsed",        "foo", conf.getProperty("test.separator.tab"));
612            assertEquals("formfeed separator not properly parsed",   "foo", conf.getProperty("test.separator.formfeed"));
613            assertEquals("whitespace separator not properly parsed", "foo", conf.getProperty("test.separator.whitespace"));
614        }
615    
616        /**
617         * Tests including properties when they are loaded from a nested directory
618         * structure.
619         */
620        @SuppressWarnings("deprecation")
621        @Test
622        public void testIncludeInSubDir() throws ConfigurationException
623        {
624            ConfigurationFactory factory = new ConfigurationFactory("conf/testFactoryPropertiesInclude.xml");
625            Configuration config = factory.getConfiguration();
626            assertTrue(config.getBoolean("deeptest"));
627            assertTrue(config.getBoolean("deepinclude"));
628            assertFalse(config.containsKey("deeptestinvalid"));
629        }
630    
631        /**
632         * Tests whether the correct line separator is used.
633         */
634        @Test
635        public void testLineSeparator() throws ConfigurationException
636        {
637            final String EOL = System.getProperty("line.separator");
638            conf = new PropertiesConfiguration();
639            conf.setHeader("My header");
640            conf.setProperty("prop", "value");
641    
642            StringWriter out = new StringWriter();
643            conf.save(out);
644            String content = out.toString();
645            assertTrue("Header could not be found", content.indexOf("# My header"
646                    + EOL + EOL) == 0);
647            assertTrue("Property could not be found", content.indexOf("prop = value" + EOL) > 0);
648        }
649    
650        /**
651         * Tests what happens if a reloading strategy's <code>reloadingRequired()</code>
652         * implementation accesses methods of the configuration that in turn cause a reload.
653         */
654        @Test
655        public void testReentrantReload()
656        {
657            conf.setProperty("shouldReload", Boolean.FALSE);
658            conf.setReloadingStrategy(new FileChangedReloadingStrategy()
659            {
660                @Override
661                public boolean reloadingRequired()
662                {
663                    return configuration.getBoolean("shouldReload");
664                }
665            });
666            assertFalse("Property has wrong value", conf.getBoolean("shouldReload"));
667        }
668    
669        /**
670         * Tests accessing the layout object.
671         */
672        @Test
673        public void testGetLayout()
674        {
675            PropertiesConfigurationLayout layout = conf.getLayout();
676            assertNotNull("Layout is null", layout);
677            assertSame("Different object returned", layout, conf.getLayout());
678            conf.setLayout(null);
679            PropertiesConfigurationLayout layout2 = conf.getLayout();
680            assertNotNull("Layout 2 is null", layout2);
681            assertNotSame("Same object returned", layout, layout2);
682        }
683    
684        /**
685         * Tests the propertyLoaded() method for a simple property.
686         */
687        @Test
688        public void testPropertyLoaded() throws ConfigurationException
689        {
690            DummyLayout layout = new DummyLayout(conf);
691            conf.setLayout(layout);
692            conf.propertyLoaded("layoutLoadedProperty", "yes");
693            assertEquals("Layout's load() was called", 0, layout.loadCalls);
694            assertEquals("Property not added", "yes", conf.getString("layoutLoadedProperty"));
695        }
696    
697        /**
698         * Tests the propertyLoaded() method for an include property.
699         */
700        @Test
701        public void testPropertyLoadedInclude() throws ConfigurationException
702        {
703            DummyLayout layout = new DummyLayout(conf);
704            conf.setLayout(layout);
705            conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClasspath.properties,testEqual.properties");
706            assertEquals("Layout's load() was not correctly called", 2, layout.loadCalls);
707            assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
708        }
709    
710        /**
711         * Tests propertyLoaded() for an include property, when includes are
712         * disabled.
713         */
714        @Test
715        public void testPropertyLoadedIncludeNotAllowed() throws ConfigurationException
716        {
717            DummyLayout layout = new DummyLayout(conf);
718            conf.setLayout(layout);
719            conf.setIncludesAllowed(false);
720            conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClassPath.properties,testEqual.properties");
721            assertEquals("Layout's load() was called", 0, layout.loadCalls);
722            assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
723        }
724    
725        /**
726         * Tests whether comment lines are correctly detected.
727         */
728        @Test
729        public void testIsCommentLine()
730        {
731            assertTrue("Comment not detected", PropertiesConfiguration.isCommentLine("# a comment"));
732            assertTrue("Alternative comment not detected", PropertiesConfiguration.isCommentLine("! a comment"));
733            assertTrue("Comment with no space not detected", PropertiesConfiguration.isCommentLine("#a comment"));
734            assertTrue("Comment with leading space not detected", PropertiesConfiguration.isCommentLine("    ! a comment"));
735            assertFalse("Wrong comment", PropertiesConfiguration.isCommentLine("   a#comment"));
736        }
737    
738        /**
739         * Tests whether a properties configuration can be successfully cloned. It
740         * is especially checked whether the layout object is taken into account.
741         */
742        @Test
743        public void testClone() throws ConfigurationException
744        {
745            PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
746            assertNotSame("Copy has same layout object", conf.getLayout(), copy.getLayout());
747            assertEquals("Wrong number of event listeners for original", 1, conf.getConfigurationListeners().size());
748            assertEquals("Wrong number of event listeners for clone", 1, copy.getConfigurationListeners().size());
749            assertSame("Wrong event listener for original", conf.getLayout(), conf.getConfigurationListeners().iterator().next());
750            assertSame("Wrong event listener for clone", copy.getLayout(), copy.getConfigurationListeners().iterator().next());
751            StringWriter outConf = new StringWriter();
752            conf.save(outConf);
753            StringWriter outCopy = new StringWriter();
754            copy.save(outCopy);
755            assertEquals("Output from copy is different", outConf.toString(), outCopy.toString());
756        }
757    
758        /**
759         * Tests the clone() method when no layout object exists yet.
760         */
761        @Test
762        public void testCloneNullLayout()
763        {
764            conf = new PropertiesConfiguration();
765            PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
766            assertNotSame("Layout objects are the same", conf.getLayout(), copy.getLayout());
767        }
768    
769        /**
770         * Tests saving a file-based configuration to a HTTP server.
771         */
772        @Test
773        public void testSaveToHTTPServerSuccess() throws Exception
774        {
775            MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
776                    HttpURLConnection.HTTP_OK, testSavePropertiesFile);
777            URL url = new URL(null, "http://jakarta.apache.org", handler);
778            conf.save(url);
779            MockHttpURLConnection con = handler.getMockConnection();
780            assertTrue("Wrong output flag", con.getDoOutput());
781            assertEquals("Wrong method", "PUT", con.getRequestMethod());
782    
783            PropertiesConfiguration checkConfig = new PropertiesConfiguration(
784                    testSavePropertiesFile);
785            ConfigurationAssert.assertEquals(conf, checkConfig);
786        }
787    
788        /**
789         * Tests saving a file-based configuration to a HTTP server when the server
790         * reports a failure. This should cause an exception.
791         */
792        @Test
793        public void testSaveToHTTPServerFail() throws Exception
794        {
795            MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
796                    HttpURLConnection.HTTP_BAD_REQUEST, testSavePropertiesFile);
797            URL url = new URL(null, "http://jakarta.apache.org", handler);
798            try
799            {
800                conf.save(url);
801                fail("Response code was not checked!");
802            }
803            catch (ConfigurationException cex)
804            {
805                assertTrue("Wrong root cause: " + cex,
806                        cex.getCause() instanceof IOException);
807            }
808        }
809    
810        /**
811         * Test the creation of a file containing a '#' in its name. This test is
812         * skipped on Java 1.3 as it always fails.
813         */
814        @Test
815        public void testFileWithSharpSymbol() throws Exception
816        {
817            if (SystemUtils.isJavaVersionAtLeast(1.4f))
818            {
819                File file = new File("target/sharp#1.properties");
820                file.createNewFile();
821    
822                PropertiesConfiguration conf = new PropertiesConfiguration(file);
823                conf.save();
824    
825                assertTrue("Missing file " + file, file.exists());
826            }
827        }
828    
829        /**
830         * Tests initializing a properties configuration from a non existing file.
831         * There was a bug, which caused properties getting lost when later save()
832         * is called.
833         */
834        @Test
835        public void testInitFromNonExistingFile() throws ConfigurationException
836        {
837            final String testProperty = "test.successfull";
838            conf = new PropertiesConfiguration(testSavePropertiesFile);
839            conf.addProperty(testProperty, Boolean.TRUE);
840            conf.save();
841            PropertiesConfiguration checkConfig = new PropertiesConfiguration(
842                    testSavePropertiesFile);
843            assertTrue("Test property not found", checkConfig
844                    .getBoolean(testProperty));
845        }
846    
847        /**
848         * Tests copying another configuration into the test configuration. This
849         * test ensures that the layout object is informed about the newly added
850         * properties.
851         */
852        @Test
853        public void testCopyAndSave() throws ConfigurationException
854        {
855            Configuration copyConf = setUpCopyConfig();
856            conf.copy(copyConf);
857            checkCopiedConfig(copyConf);
858        }
859    
860        /**
861         * Tests appending a configuration to the test configuration. Again it has
862         * to be ensured that the layout object is correctly updated.
863         */
864        @Test
865        public void testAppendAndSave() throws ConfigurationException
866        {
867            Configuration copyConf = setUpCopyConfig();
868            conf.append(copyConf);
869            checkCopiedConfig(copyConf);
870        }
871    
872        /**
873         * Tests adding properties through a DataConfiguration. This is related to
874         * CONFIGURATION-332.
875         */
876        @Test
877        public void testSaveWithDataConfig() throws ConfigurationException
878        {
879            conf = new PropertiesConfiguration(testSavePropertiesFile);
880            DataConfiguration dataConfig = new DataConfiguration(conf);
881            dataConfig.setProperty("foo", "bar");
882            assertEquals("Property not set", "bar", conf.getString("foo"));
883    
884            conf.save();
885            PropertiesConfiguration config2 = new PropertiesConfiguration(
886                    testSavePropertiesFile);
887            assertEquals("Property not saved", "bar", config2.getString("foo"));
888        }
889    
890        /**
891         * Tests whether the correct default encoding is used when loading a
892         * properties file. This test is related to CONFIGURATION-345.
893         */
894        @Test
895        public void testLoadWithDefaultEncoding() throws ConfigurationException
896        {
897            class PropertiesConfigurationTestImpl extends PropertiesConfiguration
898            {
899                String loadEncoding;
900    
901                public PropertiesConfigurationTestImpl(String fileName)
902                        throws ConfigurationException
903                {
904                    super(fileName);
905                }
906    
907                @Override
908                public void load(InputStream in, String encoding)
909                        throws ConfigurationException
910                {
911                    loadEncoding = encoding;
912                    super.load(in, encoding);
913                }
914            }
915    
916            PropertiesConfigurationTestImpl testConf = new PropertiesConfigurationTestImpl(
917                    testProperties);
918            assertEquals("Default encoding not used", "ISO-8859-1",
919                    testConf.loadEncoding);
920        }
921    
922        /**
923         * Tests whether a default IOFactory is set.
924         */
925        @Test
926        public void testGetIOFactoryDefault()
927        {
928            assertNotNull("No default IO factory", conf.getIOFactory());
929        }
930    
931        /**
932         * Tests setting the IOFactory to null. This should cause an exception.
933         */
934        @Test(expected = IllegalArgumentException.class)
935        public void testSetIOFactoryNull()
936        {
937            conf.setIOFactory(null);
938        }
939    
940        /**
941         * Tests setting an IOFactory that uses a specialized reader.
942         */
943        @Test
944        public void testSetIOFactoryReader() throws ConfigurationException
945        {
946            final int propertyCount = 10;
947            conf.clear();
948            conf.setIOFactory(new PropertiesConfiguration.IOFactory()
949            {
950                public PropertiesConfiguration.PropertiesReader createPropertiesReader(
951                        Reader in, char delimiter)
952                {
953                    return new PropertiesReaderTestImpl(in, delimiter,
954                            propertyCount);
955                }
956    
957                public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
958                        Writer out, char delimiter)
959                {
960                    throw new UnsupportedOperationException("Unexpected call!");
961                }
962            });
963            conf.load();
964            for (int i = 1; i <= propertyCount; i++)
965            {
966                assertEquals("Wrong property value at " + i, PROP_VALUE + i, conf
967                        .getString(PROP_NAME + i));
968            }
969        }
970    
971        /**
972         * Tests setting an IOFactory that uses a specialized writer.
973         */
974        @Test
975        public void testSetIOFactoryWriter() throws ConfigurationException, IOException
976        {
977            final PropertiesWriterTestImpl testWriter = new PropertiesWriterTestImpl(',');
978            conf.setIOFactory(new PropertiesConfiguration.IOFactory()
979            {
980                public PropertiesConfiguration.PropertiesReader createPropertiesReader(
981                        Reader in, char delimiter)
982                {
983                    throw new UnsupportedOperationException("Unexpected call!");
984                }
985    
986                public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
987                        Writer out, char delimiter)
988                {
989                    return testWriter;
990                }
991            });
992            conf.save(new StringWriter());
993            testWriter.close();
994            checkSavedConfig();
995        }
996    
997        /**
998         * Tests that the property separators are retained when saving the
999         * configuration.
1000         */
1001        @Test
1002        public void testKeepSeparators() throws ConfigurationException, IOException
1003        {
1004            conf.save(testSavePropertiesFile);
1005            final String[] separatorTests = {
1006                    "test.separator.equal = foo", "test.separator.colon : foo",
1007                    "test.separator.tab\tfoo", "test.separator.whitespace foo",
1008                    "test.separator.no.space=foo"
1009            };
1010            Set<String> foundLines = new HashSet<String>();
1011            BufferedReader in = new BufferedReader(new FileReader(
1012                    testSavePropertiesFile));
1013            try
1014            {
1015                String s;
1016                while ((s = in.readLine()) != null)
1017                {
1018                    for (int i = 0; i < separatorTests.length; i++)
1019                    {
1020                        if (separatorTests[i].equals(s))
1021                        {
1022                            foundLines.add(s);
1023                        }
1024                    }
1025                }
1026            }
1027            finally
1028            {
1029                in.close();
1030            }
1031            assertEquals("No all separators were found: " + foundLines,
1032                    separatorTests.length, foundLines.size());
1033        }
1034    
1035        /**
1036         * Tests whether properties with slashes in their values can be saved. This
1037         * test is related to CONFIGURATION-408.
1038         */
1039        @Test
1040        public void testSlashEscaping() throws ConfigurationException
1041        {
1042            conf.setProperty(PROP_NAME, "http://www.apache.org");
1043            StringWriter writer = new StringWriter();
1044            conf.save(writer);
1045            String s = writer.toString();
1046            assertTrue("Value not found: " + s, s.indexOf(PROP_NAME
1047                    + " = http://www.apache.org") >= 0);
1048        }
1049    
1050        /**
1051         * Tests whether backslashes are correctly handled if lists are parsed. This
1052         * test is related to CONFIGURATION-418.
1053         */
1054        @Test
1055        public void testBackslashEscapingInLists() throws Exception
1056        {
1057            checkBackslashList("share2");
1058            checkBackslashList("share1");
1059        }
1060    
1061        /**
1062         * Tests whether a list property is handled correctly if delimiter parsing
1063         * is disabled. This test is related to CONFIGURATION-495.
1064         */
1065        @Test
1066        public void testSetPropertyListWithDelimiterParsingDisabled()
1067                throws ConfigurationException
1068        {
1069            String prop = "delimiterListProp";
1070            conf.setDelimiterParsingDisabled(true);
1071            List<String> list = Arrays.asList("val", "val2", "val3");
1072            conf.setProperty(prop, list);
1073            conf.setFile(testSavePropertiesFile);
1074            conf.save();
1075            conf.clear();
1076            conf.load();
1077            assertEquals("Wrong list property", list, conf.getProperty(prop));
1078        }
1079    
1080        /**
1081         * Helper method for testing the content of a list with elements that
1082         * contain backslashes.
1083         *
1084         * @param key the key
1085         */
1086        private void checkBackslashList(String key)
1087        {
1088            Object prop = conf.getProperty("test." + key);
1089            assertTrue("Not a list", prop instanceof List);
1090            List<?> list = (List<?>) prop;
1091            assertEquals("Wrong number of list elements", 2, list.size());
1092            final String prefix = "\\\\" + key;
1093            assertEquals("Wrong element 1", prefix + "a", list.get(0));
1094            assertEquals("Wrong element 2", prefix + "b", list.get(1));
1095        }
1096    
1097        /**
1098         * Creates a configuration that can be used for testing copy operations.
1099         *
1100         * @return the configuration to be copied
1101         */
1102        private Configuration setUpCopyConfig()
1103        {
1104            final int count = 25;
1105            Configuration result = new BaseConfiguration();
1106            for (int i = 1; i <= count; i++)
1107            {
1108                result.addProperty("copyKey" + i, "copyValue" + i);
1109            }
1110            return result;
1111        }
1112    
1113        /**
1114         * Tests whether the data of a configuration that was copied into the test
1115         * configuration is correctly saved.
1116         *
1117         * @param copyConf the copied configuration
1118         * @throws ConfigurationException if an error occurs
1119         */
1120        private void checkCopiedConfig(Configuration copyConf)
1121                throws ConfigurationException
1122        {
1123            conf.save(testSavePropertiesFile);
1124            PropertiesConfiguration checkConf = new PropertiesConfiguration(
1125                    testSavePropertiesFile);
1126            for (Iterator<String> it = copyConf.getKeys(); it.hasNext();)
1127            {
1128                String key = it.next();
1129                assertEquals("Wrong value for property " + key, checkConf
1130                        .getProperty(key), copyConf.getProperty(key));
1131            }
1132        }
1133    
1134        /**
1135         * A dummy layout implementation for checking whether certain methods are
1136         * correctly called by the configuration.
1137         */
1138        static class DummyLayout extends PropertiesConfigurationLayout
1139        {
1140            /** Stores the number how often load() was called. */
1141            public int loadCalls;
1142    
1143            public DummyLayout(PropertiesConfiguration config)
1144            {
1145                super(config);
1146            }
1147    
1148            @Override
1149            public void load(Reader in) throws ConfigurationException
1150            {
1151                loadCalls++;
1152            }
1153        }
1154    
1155        /**
1156         * A mock implementation of a HttpURLConnection used for testing saving to
1157         * a HTTP server.
1158         */
1159        static class MockHttpURLConnection extends HttpURLConnection
1160        {
1161            /** The response code to return.*/
1162            private final int returnCode;
1163    
1164            /** The output file. The output stream will point to this file.*/
1165            private final File outputFile;
1166    
1167            protected MockHttpURLConnection(URL u, int respCode, File outFile)
1168            {
1169                super(u);
1170                returnCode = respCode;
1171                outputFile = outFile;
1172            }
1173    
1174            @Override
1175            public void disconnect()
1176            {
1177            }
1178    
1179            @Override
1180            public boolean usingProxy()
1181            {
1182                return false;
1183            }
1184    
1185            @Override
1186            public void connect() throws IOException
1187            {
1188            }
1189    
1190            @Override
1191            public int getResponseCode() throws IOException
1192            {
1193                return returnCode;
1194            }
1195    
1196            @Override
1197            public OutputStream getOutputStream() throws IOException
1198            {
1199                return new FileOutputStream(outputFile);
1200            }
1201        }
1202    
1203        /**
1204         * A mock stream handler for working with the mock HttpURLConnection.
1205         */
1206        static class MockHttpURLStreamHandler extends URLStreamHandler
1207        {
1208            /** Stores the response code.*/
1209            private int responseCode;
1210    
1211            /** Stores the output file.*/
1212            private File outputFile;
1213    
1214            /** Stores the connection.*/
1215            private MockHttpURLConnection connection;
1216    
1217            public MockHttpURLStreamHandler(int respCode, File outFile)
1218            {
1219                responseCode = respCode;
1220                outputFile = outFile;
1221            }
1222    
1223            public MockHttpURLConnection getMockConnection()
1224            {
1225                return connection;
1226            }
1227    
1228            @Override
1229            protected URLConnection openConnection(URL u) throws IOException
1230            {
1231                connection = new MockHttpURLConnection(u, responseCode, outputFile);
1232                return connection;
1233            }
1234        }
1235    
1236        /**
1237         * A test PropertiesReader for testing whether a custom reader can be
1238         * injected. This implementation creates a configurable number of synthetic
1239         * test properties.
1240         */
1241        private static class PropertiesReaderTestImpl extends
1242                PropertiesConfiguration.PropertiesReader
1243        {
1244            /** The number of test properties to be created. */
1245            private final int maxProperties;
1246    
1247            /** The current number of properties. */
1248            private int propertyCount;
1249    
1250            public PropertiesReaderTestImpl(Reader reader, char listDelimiter,
1251                    int maxProps)
1252            {
1253                super(reader, listDelimiter);
1254                assertEquals("Wrong list delimiter", ',', listDelimiter);
1255                maxProperties = maxProps;
1256            }
1257    
1258            @Override
1259            public String getPropertyName()
1260            {
1261                return PROP_NAME + propertyCount;
1262            }
1263    
1264            @Override
1265            public String getPropertyValue()
1266            {
1267                return PROP_VALUE + propertyCount;
1268            }
1269    
1270            @Override
1271            public boolean nextProperty() throws IOException
1272            {
1273                propertyCount++;
1274                return propertyCount <= maxProperties;
1275            }
1276        }
1277    
1278        /**
1279         * A test PropertiesWriter for testing whether a custom writer can be
1280         * injected. This implementation simply redirects all output into a test
1281         * file.
1282         */
1283        private static class PropertiesWriterTestImpl extends
1284                PropertiesConfiguration.PropertiesWriter
1285        {
1286            public PropertiesWriterTestImpl(char delimiter) throws IOException
1287            {
1288                super(new FileWriter(testSavePropertiesFile), delimiter);
1289            }
1290        }
1291    }