1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.io.Reader;
28  import java.io.StringReader;
29  import java.io.StringWriter;
30  import java.net.HttpURLConnection;
31  import java.net.URL;
32  import java.net.URLConnection;
33  import java.net.URLStreamHandler;
34  import java.util.ArrayList;
35  import java.util.Iterator;
36  import java.util.List;
37  
38  import junit.framework.TestCase;
39  
40  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
41  import org.apache.commons.lang.SystemUtils;
42  
43  /***
44   * Test for loading and saving properties files.
45   *
46   * @version $Id: TestPropertiesConfiguration.java 712431 2008-11-08 21:05:23Z oheger $
47   */
48  public class TestPropertiesConfiguration extends TestCase
49  {
50      private PropertiesConfiguration conf;
51  
52      /*** The File that we test with */
53      private String testProperties = new File("conf/test.properties").getAbsolutePath();
54  
55      private String testBasePath = new File("conf").getAbsolutePath();
56      private String testBasePath2 = new File("conf").getAbsoluteFile().getParentFile().getAbsolutePath();
57      private File testSavePropertiesFile = new File("target/testsave.properties");
58  
59      protected void setUp() throws Exception
60      {
61          conf = new PropertiesConfiguration(testProperties);
62  
63          // remove the test save file if it exists
64          if (testSavePropertiesFile.exists())
65          {
66              assertTrue("Test output file could not be deleted",
67                      testSavePropertiesFile.delete());
68          }
69      }
70  
71      public void testLoad() throws Exception
72      {
73          String loaded = conf.getString("configuration.loaded");
74          assertEquals("true", loaded);
75      }
76  
77      /***
78       * Tests if properties can be appended by simply calling load() another
79       * time.
80       */
81      public void testAppend() throws Exception
82      {
83          File file2 = new File("conf/threesome.properties");
84          conf.load(file2);
85          assertEquals("aaa", conf.getString("test.threesome.one"));
86          assertEquals("true", conf.getString("configuration.loaded"));
87      }
88  
89      /***
90       * Tests that empty properties are treated as the empty string
91       * (rather than as null).
92       */
93      public void testEmpty() throws Exception
94      {
95          String empty = conf.getString("test.empty");
96          assertNotNull(empty);
97          assertEquals("", empty);
98      }
99  
100     /***
101      * Tests that references to other properties work
102      */
103     public void testReference() throws Exception
104     {
105         assertEquals("baseextra", conf.getString("base.reference"));
106     }
107 
108     /***
109      * test if includes properties get loaded too
110      */
111     public void testLoadInclude() throws Exception
112     {
113         String loaded = conf.getString("include.loaded");
114         assertEquals("true", loaded);
115     }
116 
117     /***
118      * test if includes properties from interpolated file
119      * name get loaded
120      */
121     public void testLoadIncludeInterpol() throws Exception
122     {
123         String loaded = conf.getString("include.interpol.loaded");
124         assertEquals("true", loaded);
125     }
126 
127     public void testSetInclude() throws Exception
128     {
129         // change the include key
130         PropertiesConfiguration.setInclude("import");
131 
132         // load the configuration
133         PropertiesConfiguration conf = new PropertiesConfiguration();
134         conf.load("conf/test.properties");
135 
136         // restore the previous value for the other tests
137         PropertiesConfiguration.setInclude("include");
138 
139         assertNull(conf.getString("include.loaded"));
140     }
141 
142     /***
143      * Tests <code>List</code> parsing.
144      */
145     public void testList() throws Exception
146     {
147         List packages = conf.getList("packages");
148         // we should get 3 packages here
149         assertEquals(3, packages.size());
150     }
151 
152     public void testSave() throws Exception
153     {
154         // add an array of strings to the configuration
155         conf.addProperty("string", "value1");
156         List list = new ArrayList();
157         for (int i = 1; i < 5; i++)
158         {
159             list.add("value" + i);
160         }
161         conf.addProperty("array", list);
162 
163         // save the configuration
164         String filename = testSavePropertiesFile.getAbsolutePath();
165         conf.save(filename);
166 
167         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
168 
169         // read the configuration and compare the properties
170         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
171         ConfigurationAssert.assertEquals(conf, checkConfig);
172 
173         // Save it again, verifing a save with a filename works.
174         checkConfig.save();
175     }
176 
177     public void testSaveToCustomURL() throws Exception
178     {
179         // save the configuration to a custom URL
180         URL url = new URL("foo", "", 0, "./target/testsave-custom-url.properties", new FileURLStreamHandler());
181         conf.save(url);
182 
183         // reload the configuration
184         Configuration config2 = new PropertiesConfiguration(url);
185         assertEquals("true", config2.getString("configuration.loaded"));
186     }
187 
188     public void testInMemoryCreatedSave() throws Exception
189     {
190         PropertiesConfiguration pc = new PropertiesConfiguration();
191         // add an array of strings to the configuration
192         pc.addProperty("string", "value1");
193         List list = new ArrayList();
194         for (int i = 1; i < 5; i++)
195         {
196             list.add("value" + i);
197         }
198         pc.addProperty("array", list);
199 
200         // save the configuration
201         String filename = testSavePropertiesFile.getAbsolutePath();
202         pc.save(filename);
203 
204         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
205 
206         // read the configuration and compare the properties
207         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
208         ConfigurationAssert.assertEquals(pc, checkConfig);
209 
210         // Save it again, verifing a save with a filename works.
211         checkConfig.save();
212     }
213 
214     /***
215      * Tests saving a configuration when delimiter parsing is disabled.
216      */
217     public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException
218     {
219         conf.clear();
220         conf.setDelimiterParsingDisabled(true);
221         conf.addProperty("test.list", "a,b,c");
222         conf.addProperty("test.dirs", "C://Temp//,D://Data//");
223         conf.save(testSavePropertiesFile);
224 
225         PropertiesConfiguration checkConfig = new PropertiesConfiguration();
226         checkConfig.setDelimiterParsingDisabled(true);
227         checkConfig.setFile(testSavePropertiesFile);
228         checkConfig.load();
229         ConfigurationAssert.assertEquals(conf, checkConfig);
230     }
231 
232     public void testSaveMissingFilename()
233     {
234         PropertiesConfiguration pc = new PropertiesConfiguration();
235         try
236         {
237             pc.save();
238             fail("Should have throw ConfigurationException");
239         }
240         catch (ConfigurationException ce)
241         {
242             //good
243         }
244     }
245 
246     /***
247      * Tests if the base path is taken into account by the save() method.
248      * @throws Exception if an error occurs
249      */
250     public void testSaveWithBasePath() throws Exception
251     {
252         conf.setProperty("test", "true");
253         conf.setBasePath(testSavePropertiesFile.getParentFile().toURL().toString());
254         conf.setFileName(testSavePropertiesFile.getName());
255         conf.save();
256         assertTrue(testSavePropertiesFile.exists());
257     }
258 
259     /***
260      * Tests whether the escape character for list delimiters can be itself
261      * escaped and survives a save operation.
262      */
263     public void testSaveEscapedEscapingCharacter()
264             throws ConfigurationException
265     {
266         conf.addProperty("test.dirs", "C://Temp////,D://Data////,E://Test//");
267         List dirs = conf.getList("test.dirs");
268         assertEquals("Wrong number of list elements", 3, dirs.size());
269         conf.save(testSavePropertiesFile);
270 
271         PropertiesConfiguration checkConfig = new PropertiesConfiguration(
272                 testSavePropertiesFile);
273         ConfigurationAssert.assertEquals(conf, checkConfig);
274     }
275 
276     public void testLoadViaProperty() throws Exception
277     {
278         PropertiesConfiguration pc = new PropertiesConfiguration();
279         pc.setFileName(testProperties);
280         pc.load();
281 
282         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
283     }
284 
285     public void testLoadViaPropertyWithBasePath() throws Exception
286     {
287         PropertiesConfiguration pc = new PropertiesConfiguration();
288         pc.setBasePath(testBasePath);
289         pc.setFileName("test.properties");
290         pc.load();
291 
292         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
293     }
294 
295     public void testLoadViaPropertyWithBasePath2() throws Exception
296     {
297         PropertiesConfiguration pc = new PropertiesConfiguration();
298         pc.setBasePath(testBasePath2);
299         pc.setFileName("conf/test.properties");
300         pc.load();
301 
302         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
303 
304         pc = new PropertiesConfiguration();
305         pc.setBasePath(testBasePath2);
306         pc.setFileName("conf/test.properties");
307         pc.load();
308 
309         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
310     }
311 
312     public void testLoadFromFile() throws Exception
313     {
314         File file = new File("conf/test.properties");
315         conf = new PropertiesConfiguration(file);
316 
317         assertEquals("true", conf.getString("configuration.loaded"));
318     }
319 
320     public void testLoadUnexistingFile()
321     {
322         try
323         {
324             conf = new PropertiesConfiguration("Unexisting file");
325             fail("Unexisting file was loaded.");
326         }
327         catch(ConfigurationException cex)
328         {
329             // fine
330         }
331     }
332 
333     /***
334      * Tests to load a file with enabled auto save mode.
335      */
336     public void testLoadWithAutoSave() throws Exception
337     {
338         setUpSavedProperties();
339     }
340 
341     /***
342      * Tests the auto save functionality when an existing property is modified.
343      */
344     public void testLoadWithAutoSaveAndSetExisting() throws Exception
345     {
346         setUpSavedProperties();
347         conf.setProperty("a", "moreThanOne");
348         checkSavedConfig();
349     }
350 
351     /***
352      * Tests the auto save functionality when a new property is added using the
353      * setProperty() method.
354      */
355     public void testLoadWithAutoSaveAndSetNew() throws Exception
356     {
357         setUpSavedProperties();
358         conf.setProperty("d", "four");
359         checkSavedConfig();
360     }
361 
362     /***
363      * Tests the auto save functionality when a new property is added using the
364      * addProperty() method.
365      */
366     public void testLoadWithAutoSaveAndAdd() throws Exception
367     {
368         setUpSavedProperties();
369         conf.addProperty("d", "four");
370         checkSavedConfig();
371     }
372 
373     /***
374      * Tests the auto save functionality when a property is removed.
375      */
376     public void testLoadWithAutoSaveAndClear() throws Exception
377     {
378         setUpSavedProperties();
379         conf.clearProperty("c");
380         PropertiesConfiguration checkConfig = checkSavedConfig();
381         assertFalse("The saved configuration contain the key '" + "c" + "'", checkConfig.containsKey("c"));
382     }
383 
384     /***
385      * Creates a properties file on disk. Used for testing load and save
386      * operations.
387      *
388      * @throws IOException if an I/O error occurs
389      */
390     private void setUpSavedProperties() throws IOException, ConfigurationException
391     {
392         PrintWriter out = null;
393 
394         try
395         {
396             out = new PrintWriter(new FileWriter(testSavePropertiesFile));
397             out.println("a = one");
398             out.println("b = two");
399             out.println("c = three");
400             out.close();
401             out = null;
402 
403             conf = new PropertiesConfiguration();
404             conf.setAutoSave(true);
405             conf.setFile(testSavePropertiesFile);
406             conf.load();
407             assertEquals("one", conf.getString("a"));
408             assertEquals("two", conf.getString("b"));
409             assertEquals("three", conf.getString("c"));
410         }
411         finally
412         {
413             if (out != null)
414             {
415                 out.close();
416             }
417         }
418     }
419 
420     /***
421      * Helper method for testing a saved configuration. Reads in the file using
422      * a new instance and compares this instance with the original one.
423      *
424      * @return the newly created configuration instance
425      * @throws ConfigurationException if an error occurs
426      */
427     private PropertiesConfiguration checkSavedConfig()
428             throws ConfigurationException
429     {
430         PropertiesConfiguration checkConfig = new PropertiesConfiguration(testSavePropertiesFile);
431         for (Iterator i = conf.getKeys(); i.hasNext();)
432         {
433             String key = (String) i.next();
434             assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key));
435             assertEquals("Value of the '" + key + "' property", conf.getProperty(key), checkConfig.getProperty(key));
436         }
437         return checkConfig;
438     }
439 
440     public void testGetStringWithEscapedChars()
441     {
442         String property = conf.getString("test.unescape");
443         assertEquals("String with escaped characters", "This \n string \t contains \" escaped // characters", property);
444     }
445 
446     public void testGetStringWithEscapedComma()
447     {
448         String property = conf.getString("test.unescape.list-separator");
449         assertEquals("String with an escaped list separator", "This string contains , an escaped list separator", property);
450     }
451 
452     public void testUnescapeJava()
453     {
454         assertEquals("test//,test", PropertiesConfiguration.unescapeJava("test//,test", ','));
455     }
456 
457     public void testEscapedKey() throws Exception
458     {
459         PropertiesConfiguration conf = new PropertiesConfiguration();
460         conf.load(new StringReader("//u0066//u006f//u006f=bar"));
461 
462         assertEquals("value of the 'foo' property", "bar", conf.getString("foo"));
463     }
464 
465     public void testMixedArray()
466     {
467         String[] array = conf.getStringArray("test.mixed.array");
468 
469         assertEquals("array length", 4, array.length);
470         assertEquals("1st element", "a", array[0]);
471         assertEquals("2nd element", "b", array[1]);
472         assertEquals("3rd element", "c", array[2]);
473         assertEquals("4th element", "d", array[3]);
474     }
475 
476     public void testMultilines()
477     {
478         String property = "This is a value spread out across several adjacent "
479                 + "natural lines by escaping the line terminator with "
480                 + "a backslash character.";
481 
482         assertEquals("'test.multilines' property", property, conf.getString("test.multilines"));
483     }
484 
485     public void testChangingDefaultListDelimiter() throws Exception
486     {
487         PropertiesConfiguration pc = new PropertiesConfiguration(testProperties);
488         assertEquals(4, pc.getList("test.mixed.array").size());
489 
490         char delimiter = PropertiesConfiguration.getDefaultListDelimiter();
491         PropertiesConfiguration.setDefaultListDelimiter('^');
492         pc = new PropertiesConfiguration(testProperties);
493         assertEquals(2, pc.getList("test.mixed.array").size());
494         PropertiesConfiguration.setDefaultListDelimiter(delimiter);
495     }
496 
497     public void testChangingListDelimiter() throws Exception
498     {
499         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
500         assertEquals(4, pc1.getList("test.mixed.array").size());
501 
502         PropertiesConfiguration pc2 = new PropertiesConfiguration();
503         pc2.setListDelimiter('^');
504         pc2.setFileName(testProperties);
505         pc2.load();
506         assertEquals("Should obtain the first value", "a", pc2.getString("test.mixed.array"));
507         assertEquals(2, pc2.getList("test.mixed.array").size());
508     }
509 
510     public void testDisableListDelimiter() throws Exception
511     {
512         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
513         assertEquals(4, pc1.getList("test.mixed.array").size());
514 
515         PropertiesConfiguration pc2 = new PropertiesConfiguration();
516         pc2.setDelimiterParsingDisabled(true);
517         pc2.setFileName(testProperties);
518         pc2.load();
519         assertEquals(2, pc2.getList("test.mixed.array").size());
520     }
521 
522     /***
523      * Tests escaping of an end of line with a backslash.
524      */
525     public void testNewLineEscaping()
526     {
527         List list = conf.getList("test.path");
528         assertEquals(3, list.size());
529         assertEquals("C://path1//", list.get(0));
530         assertEquals("C://path2//", list.get(1));
531         assertEquals("C://path3//complex//test//", list.get(2));
532     }
533 
534     /***
535      * Tests if included files are loaded when the source lies in the class path.
536      */
537     public void testLoadIncludeFromClassPath() throws ConfigurationException
538     {
539         conf = new PropertiesConfiguration("test.properties");
540         assertEquals("true", conf.getString("include.loaded"));
541     }
542 
543     /***
544      * Test if the lines starting with # or ! are properly ignored.
545      */
546     public void testComment() {
547         assertFalse("comment line starting with '#' parsed as a property", conf.containsKey("#comment"));
548         assertFalse("comment line starting with '!' parsed as a property", conf.containsKey("!comment"));
549     }
550 
551     /***
552      * Check that key/value separators can be part of a key.
553      */
554     public void testEscapedKeyValueSeparator()
555     {
556         assertEquals("Escaped separator '=' not supported in keys", "foo", conf.getProperty("test.separator=in.key"));
557         assertEquals("Escaped separator ':' not supported in keys", "bar", conf.getProperty("test.separator:in.key"));
558         assertEquals("Escaped separator '//t' not supported in keys", "foo", conf.getProperty("test.separator\tin.key"));
559         assertEquals("Escaped separator '//f' not supported in keys", "bar", conf.getProperty("test.separator\fin.key"));
560         assertEquals("Escaped separator ' ' not supported in keys"  , "foo", conf.getProperty("test.separator in.key"));
561     }
562 
563     /***
564      * Test all acceptable key/value separators ('=', ':' or white spaces).
565      */
566     public void testKeyValueSeparators() {
567         assertEquals("equal separator not properly parsed",      "foo", conf.getProperty("test.separator.equal"));
568         assertEquals("colon separator not properly parsed",      "foo", conf.getProperty("test.separator.colon"));
569         assertEquals("tab separator not properly parsed",        "foo", conf.getProperty("test.separator.tab"));
570         assertEquals("formfeed separator not properly parsed",   "foo", conf.getProperty("test.separator.formfeed"));
571         assertEquals("whitespace separator not properly parsed", "foo", conf.getProperty("test.separator.whitespace"));
572     }
573 
574     /***
575      * Tests including properties when they are loaded from a nested directory
576      * structure.
577      */
578     public void testIncludeInSubDir() throws ConfigurationException
579     {
580         ConfigurationFactory factory = new ConfigurationFactory("conf/testFactoryPropertiesInclude.xml");
581         Configuration config = factory.getConfiguration();
582         assertEquals(true, config.getBoolean("deeptest"));
583         assertEquals(true, config.getBoolean("deepinclude"));
584         assertFalse(config.containsKey("deeptestinvalid"));
585     }
586 
587     /***
588      * Tests whether the correct line separator is used.
589      */
590     public void testLineSeparator() throws ConfigurationException
591     {
592         final String EOL = System.getProperty("line.separator");
593         conf = new PropertiesConfiguration();
594         conf.setHeader("My header");
595         conf.setProperty("prop", "value");
596 
597         StringWriter out = new StringWriter();
598         conf.save(out);
599         String content = out.toString();
600         assertTrue("Header could not be found", content.indexOf("# My header"
601                 + EOL + EOL) == 0);
602         assertTrue("Property could not be found", content.indexOf("prop = value" + EOL) > 0);
603     }
604 
605     /***
606      * Tests what happens if a reloading strategy's <code>reloadingRequired()</code>
607      * implementation accesses methods of the configuration that in turn cause a reload.
608      */
609     public void testReentrantReload()
610     {
611         conf.setProperty("shouldReload", Boolean.FALSE);
612         conf.setReloadingStrategy(new FileChangedReloadingStrategy()
613         {
614             public boolean reloadingRequired()
615             {
616                 return configuration.getBoolean("shouldReload");
617             }
618         });
619         assertFalse("Property has wrong value", conf.getBoolean("shouldReload"));
620     }
621 
622     /***
623      * Tests accessing the layout object.
624      */
625     public void testGetLayout()
626     {
627         PropertiesConfigurationLayout layout = conf.getLayout();
628         assertNotNull("Layout is null", layout);
629         assertSame("Different object returned", layout, conf.getLayout());
630         conf.setLayout(null);
631         PropertiesConfigurationLayout layout2 = conf.getLayout();
632         assertNotNull("Layout 2 is null", layout2);
633         assertNotSame("Same object returned", layout, layout2);
634     }
635 
636     /***
637      * Tests the propertyLoaded() method for a simple property.
638      */
639     public void testPropertyLoaded() throws ConfigurationException
640     {
641         DummyLayout layout = new DummyLayout(conf);
642         conf.setLayout(layout);
643         conf.propertyLoaded("layoutLoadedProperty", "yes");
644         assertEquals("Layout's load() was called", 0, layout.loadCalls);
645         assertEquals("Property not added", "yes", conf.getString("layoutLoadedProperty"));
646     }
647 
648     /***
649      * Tests the propertyLoaded() method for an include property.
650      */
651     public void testPropertyLoadedInclude() throws ConfigurationException
652     {
653         DummyLayout layout = new DummyLayout(conf);
654         conf.setLayout(layout);
655         conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClasspath.properties,testEqual.properties");
656         assertEquals("Layout's load() was not correctly called", 2, layout.loadCalls);
657         assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
658     }
659 
660     /***
661      * Tests propertyLoaded() for an include property, when includes are
662      * disabled.
663      */
664     public void testPropertyLoadedIncludeNotAllowed() throws ConfigurationException
665     {
666         DummyLayout layout = new DummyLayout(conf);
667         conf.setLayout(layout);
668         conf.setIncludesAllowed(false);
669         conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClassPath.properties,testEqual.properties");
670         assertEquals("Layout's load() was called", 0, layout.loadCalls);
671         assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
672     }
673 
674     /***
675      * Tests whether comment lines are correctly detected.
676      */
677     public void testIsCommentLine()
678     {
679         assertTrue("Comment not detected", PropertiesConfiguration.isCommentLine("# a comment"));
680         assertTrue("Alternative comment not detected", PropertiesConfiguration.isCommentLine("! a comment"));
681         assertTrue("Comment with no space not detected", PropertiesConfiguration.isCommentLine("#a comment"));
682         assertTrue("Comment with leading space not detected", PropertiesConfiguration.isCommentLine("    ! a comment"));
683         assertFalse("Wrong comment", PropertiesConfiguration.isCommentLine("   a#comment"));
684     }
685 
686     /***
687      * Tests whether a properties configuration can be successfully cloned. It
688      * is especially checked whether the layout object is taken into account.
689      */
690     public void testClone() throws ConfigurationException
691     {
692         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
693         assertNotSame("Copy has same layout object", conf.getLayout(), copy.getLayout());
694         assertEquals("Wrong number of event listeners for original", 1, conf.getConfigurationListeners().size());
695         assertEquals("Wrong number of event listeners for clone", 1, copy.getConfigurationListeners().size());
696         assertSame("Wrong event listener for original", conf.getLayout(), conf.getConfigurationListeners().iterator().next());
697         assertSame("Wrong event listener for clone", copy.getLayout(), copy.getConfigurationListeners().iterator().next());
698         StringWriter outConf = new StringWriter();
699         conf.save(outConf);
700         StringWriter outCopy = new StringWriter();
701         copy.save(outCopy);
702         assertEquals("Output from copy is different", outConf.toString(), outCopy.toString());
703     }
704 
705     /***
706      * Tests the clone() method when no layout object exists yet.
707      */
708     public void testCloneNullLayout()
709     {
710         conf = new PropertiesConfiguration();
711         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
712         assertNotSame("Layout objects are the same", conf.getLayout(), copy.getLayout());
713     }
714 
715     /***
716      * Tests saving a file-based configuration to a HTTP server.
717      */
718     public void testSaveToHTTPServerSuccess() throws Exception
719     {
720         MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
721                 HttpURLConnection.HTTP_OK, testSavePropertiesFile);
722         URL url = new URL(null, "http://jakarta.apache.org", handler);
723         conf.save(url);
724         MockHttpURLConnection con = handler.getMockConnection();
725         assertTrue("Wrong output flag", con.getDoOutput());
726         assertEquals("Wrong method", "PUT", con.getRequestMethod());
727 
728         PropertiesConfiguration checkConfig = new PropertiesConfiguration(
729                 testSavePropertiesFile);
730         ConfigurationAssert.assertEquals(conf, checkConfig);
731     }
732 
733     /***
734      * Tests saving a file-based configuration to a HTTP server when the server
735      * reports a failure. This should cause an exception.
736      */
737     public void testSaveToHTTPServerFail() throws Exception
738     {
739         MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
740                 HttpURLConnection.HTTP_BAD_REQUEST, testSavePropertiesFile);
741         URL url = new URL(null, "http://jakarta.apache.org", handler);
742         try
743         {
744             conf.save(url);
745             fail("Response code was not checked!");
746         }
747         catch (ConfigurationException cex)
748         {
749             assertTrue("Wrong root cause: " + cex,
750                     cex.getCause() instanceof IOException);
751         }
752     }
753 
754     /***
755      * Test the creation of a file containing a '#' in its name. This test is
756      * skipped on Java 1.3 as it always fails.
757      */
758     public void testFileWithSharpSymbol() throws Exception
759     {
760         if (SystemUtils.isJavaVersionAtLeast(1.4f))
761         {
762             File file = new File("target/sharp#1.properties");
763             file.createNewFile();
764 
765             PropertiesConfiguration conf = new PropertiesConfiguration(file);
766             conf.save();
767 
768             assertTrue("Missing file " + file, file.exists());
769         }
770     }
771 
772     /***
773      * Tests initializing a properties configuration from a non existing file.
774      * There was a bug, which caused properties getting lost when later save()
775      * is called.
776      */
777     public void testInitFromNonExistingFile() throws ConfigurationException
778     {
779         final String testProperty = "test.successfull";
780         conf = new PropertiesConfiguration(testSavePropertiesFile);
781         conf.addProperty(testProperty, Boolean.TRUE);
782         conf.save();
783         PropertiesConfiguration checkConfig = new PropertiesConfiguration(
784                 testSavePropertiesFile);
785         assertTrue("Test property not found", checkConfig
786                 .getBoolean(testProperty));
787     }
788 
789     /***
790      * Tests copying another configuration into the test configuration. This
791      * test ensures that the layout object is informed about the newly added
792      * properties.
793      */
794     public void testCopyAndSave() throws ConfigurationException
795     {
796         Configuration copyConf = setUpCopyConfig();
797         conf.copy(copyConf);
798         checkCopiedConfig(copyConf);
799     }
800 
801     /***
802      * Tests appending a configuration to the test configuration. Again it has
803      * to be ensured that the layout object is correctly updated.
804      */
805     public void testAppendAndSave() throws ConfigurationException
806     {
807         Configuration copyConf = setUpCopyConfig();
808         conf.append(copyConf);
809         checkCopiedConfig(copyConf);
810     }
811 
812     /***
813      * Tests adding properties through a DataConfiguration. This is related to
814      * CONFIGURATION-332.
815      */
816     public void testSaveWithDataConfig() throws ConfigurationException
817     {
818         conf = new PropertiesConfiguration(testSavePropertiesFile);
819         DataConfiguration dataConfig = new DataConfiguration(conf);
820         dataConfig.setProperty("foo", "bar");
821         assertEquals("Property not set", "bar", conf.getString("foo"));
822 
823         conf.save();
824         PropertiesConfiguration config2 = new PropertiesConfiguration(
825                 testSavePropertiesFile);
826         assertEquals("Property not saved", "bar", config2.getString("foo"));
827     }
828 
829     /***
830      * Tests whether the correct default encoding is used when loading a
831      * properties file. This test is related to CONFIGURATION-345.
832      */
833     public void testLoadWithDefaultEncoding() throws ConfigurationException
834     {
835         class PropertiesConfigurationTestImpl extends PropertiesConfiguration
836         {
837             String loadEncoding;
838 
839             public PropertiesConfigurationTestImpl(String fileName)
840                     throws ConfigurationException
841             {
842                 super(fileName);
843             }
844 
845             public void load(InputStream in, String encoding)
846                     throws ConfigurationException
847             {
848                 loadEncoding = encoding;
849                 super.load(in, encoding);
850             }
851         }
852 
853         PropertiesConfigurationTestImpl testConf = new PropertiesConfigurationTestImpl(
854                 testProperties);
855         assertEquals("Default encoding not used", "ISO-8859-1",
856                 testConf.loadEncoding);
857     }
858 
859     /***
860      * Creates a configuration that can be used for testing copy operations.
861      *
862      * @return the configuration to be copied
863      */
864     private Configuration setUpCopyConfig()
865     {
866         final int count = 25;
867         Configuration result = new BaseConfiguration();
868         for (int i = 1; i <= count; i++)
869         {
870             result.addProperty("copyKey" + i, "copyValue" + i);
871         }
872         return result;
873     }
874 
875     /***
876      * Tests whether the data of a configuration that was copied into the test
877      * configuration is correctly saved.
878      *
879      * @param copyConf the copied configuration
880      * @throws ConfigurationException if an error occurs
881      */
882     private void checkCopiedConfig(Configuration copyConf)
883             throws ConfigurationException
884     {
885         conf.save(testSavePropertiesFile);
886         PropertiesConfiguration checkConf = new PropertiesConfiguration(
887                 testSavePropertiesFile);
888         for (Iterator it = copyConf.getKeys(); it.hasNext();)
889         {
890             String key = (String) it.next();
891             assertEquals("Wrong value for property " + key, checkConf
892                     .getProperty(key), copyConf.getProperty(key));
893         }
894     }
895 
896     /***
897      * A dummy layout implementation for checking whether certain methods are
898      * correctly called by the configuration.
899      */
900     static class DummyLayout extends PropertiesConfigurationLayout
901     {
902         /*** Stores the number how often load() was called. */
903         public int loadCalls;
904 
905         public DummyLayout(PropertiesConfiguration config)
906         {
907             super(config);
908         }
909 
910         public void load(Reader in) throws ConfigurationException
911         {
912             loadCalls++;
913         }
914     }
915 
916     /***
917      * A mock implementation of a HttpURLConnection used for testing saving to
918      * a HTTP server.
919      */
920     static class MockHttpURLConnection extends HttpURLConnection
921     {
922         /*** The response code to return.*/
923         private int responseCode;
924 
925         /*** The output file. The output stream will point to this file.*/
926         private File outputFile;
927 
928         protected MockHttpURLConnection(URL u, int respCode, File outFile)
929         {
930             super(u);
931             responseCode = respCode;
932             outputFile = outFile;
933         }
934 
935         public void disconnect()
936         {
937         }
938 
939         public boolean usingProxy()
940         {
941             return false;
942         }
943 
944         public void connect() throws IOException
945         {
946         }
947 
948         public int getResponseCode() throws IOException
949         {
950             return responseCode;
951         }
952 
953         public OutputStream getOutputStream() throws IOException
954         {
955             return new FileOutputStream(outputFile);
956         }
957     }
958 
959     /***
960      * A mock stream handler for working with the mock HttpURLConnection.
961      */
962     static class MockHttpURLStreamHandler extends URLStreamHandler
963     {
964         /*** Stores the response code.*/
965         private int responseCode;
966 
967         /*** Stores the output file.*/
968         private File outputFile;
969 
970         /*** Stores the connection.*/
971         private MockHttpURLConnection connection;
972 
973         public MockHttpURLStreamHandler(int respCode, File outFile)
974         {
975             responseCode = respCode;
976             outputFile = outFile;
977         }
978 
979         public MockHttpURLConnection getMockConnection()
980         {
981             return connection;
982         }
983 
984         protected URLConnection openConnection(URL u) throws IOException
985         {
986             connection = new MockHttpURLConnection(u, responseCode, outputFile);
987             return connection;
988         }
989     }
990 }