View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertNotNull;
23  import static org.junit.Assert.assertNotSame;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertSame;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import java.io.BufferedReader;
30  import java.io.File;
31  import java.io.FileOutputStream;
32  import java.io.FileReader;
33  import java.io.FileWriter;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.OutputStream;
37  import java.io.PrintWriter;
38  import java.io.Reader;
39  import java.io.StringReader;
40  import java.io.StringWriter;
41  import java.io.Writer;
42  import java.net.HttpURLConnection;
43  import java.net.URL;
44  import java.net.URLConnection;
45  import java.net.URLStreamHandler;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Set;
52  
53  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
54  import org.apache.commons.lang.SystemUtils;
55  import org.junit.Before;
56  import org.junit.Test;
57  
58  /**
59   * Test for loading and saving properties files.
60   *
61   * @version $Id: TestPropertiesConfiguration.java 1330666 2012-04-26 06:12:30Z oheger $
62   */
63  public class TestPropertiesConfiguration
64  {
65      /** Constant for a test property name.*/
66      private static final String PROP_NAME = "testProperty";
67  
68      /** Constant for a test property value.*/
69      private static final String PROP_VALUE = "value";
70  
71      /** The configuration to be tested.*/
72      private PropertiesConfiguration conf;
73  
74      /** The File that we test with */
75      private static String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
76  
77      private static String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
78      private static String testBasePath2 = ConfigurationAssert.TEST_DIR.getParentFile().getAbsolutePath();
79      private static File testSavePropertiesFile = ConfigurationAssert.getOutFile("testsave.properties");
80  
81      @Before
82      public void setUp() throws Exception
83      {
84          conf = new PropertiesConfiguration(testProperties);
85  
86          // remove the test save file if it exists
87          if (testSavePropertiesFile.exists())
88          {
89              assertTrue("Test output file could not be deleted",
90                      testSavePropertiesFile.delete());
91          }
92      }
93  
94      @Test
95      public void testLoad() throws Exception
96      {
97          String loaded = conf.getString("configuration.loaded");
98          assertEquals("true", loaded);
99      }
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 }