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