    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.FileWriter;
    22  import java.io.IOException;
    23  import java.io.PrintWriter;
    24  import java.io.Reader;
    25  import java.io.StringReader;
    26  import java.io.StringWriter;
    27  import java.util.ArrayList;
    28  import java.util.Iterator;
    29  import java.util.List;
    30
    31  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
    32
    33  import junit.framework.TestCase;
    34
    35  /***
    36   * Test for loading and saving properties files.
    37   *
    38   * @version $Id: TestPropertiesConfiguration.java 439648 2006-09-02 20:42:10Z oheger $
    39   */
    40  public class TestPropertiesConfiguration extends TestCase
    41  {
    42      private PropertiesConfiguration conf;
    43
    44      /*** The File that we test with */
    45      private String testProperties = new File("conf/test.properties").getAbsolutePath();
    46
    47      private String testBasePath = new File("conf").getAbsolutePath();
    48      private String testBasePath2 = new File("conf").getAbsoluteFile().getParentFile().getAbsolutePath();
    49      private File testSavePropertiesFile = new File("target/testsave.properties");
    50
    51      protected void setUp() throws Exception
    52      {
    53          conf = new PropertiesConfiguration(testProperties);
    54      }
    55
    56      public void testLoad() throws Exception
    57      {
    58          String loaded = conf.getString("configuration.loaded");
    59          assertEquals("true", loaded);
    60      }
    61
    62      /***
    63       * Tests if properties can be appended by simply calling load() another
    64       * time.
    65       */
    66      public void testAppend() throws Exception
    67      {
    68          File file2 = new File("conf/threesome.properties");
    69          conf.load(file2);
    70          assertEquals("aaa", conf.getString("test.threesome.one"));
    71          assertEquals("true", conf.getString("configuration.loaded"));
    72      }
    73
    74      /***
    75       * Tests that empty properties are treated as the empty string
    76       * (rather than as null).
    77       */
    78      public void testEmpty() throws Exception
    79      {
    80          String empty = conf.getString("test.empty");
    81          assertNotNull(empty);
    82          assertEquals("", empty);
    83      }
    84
    85      /***
    86       * Tests that references to other properties work
    87       */
    88      public void testReference() throws Exception
    89      {
    90          assertEquals("baseextra", conf.getString("base.reference"));
    91      }
    92
    93      /***
    94       * test if includes properties get loaded too
    95       */
    96      public void testLoadInclude() throws Exception
    97      {
    98          String loaded = conf.getString("include.loaded");
    99          assertEquals("true", loaded);
    100     }
    101
    102     public void testSetInclude() throws Exception
    103     {
    104         // change the include key
    105         PropertiesConfiguration.setInclude("import");
    106
    107         // load the configuration
    108         PropertiesConfiguration conf = new PropertiesConfiguration();
    109         conf.load("conf/test.properties");
    110
    111         // restore the previous value for the other tests
    112         PropertiesConfiguration.setInclude("include");
    113
    114         assertNull(conf.getString("include.loaded"));
    115     }
    116
    117     /***
    118      * Tests <code>List</code> parsing.
    119      */
    120     public void testList() throws Exception
    121     {
    122         List packages = conf.getList("packages");
    123         // we should get 3 packages here
    124         assertEquals(3, packages.size());
    125     }
    126
    127     public void testSave() throws Exception
    128     {
    129         // remove the file previously saved if necessary
    130         if (testSavePropertiesFile.exists())
    131         {
    132             assertTrue(testSavePropertiesFile.delete());
    133         }
    134
    135         // add an array of strings to the configuration
    136         conf.addProperty("string", "value1");
    137         List list = new ArrayList();
    138         for (int i = 1; i < 5; i++)
    139         {
    140             list.add("value" + i);
    141         }
    142         conf.addProperty("array", list);
    143
    144         // save the configuration
    145         String filename = testSavePropertiesFile.getAbsolutePath();
    146         conf.save(filename);
    147
    148         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
    149
    150         // read the configuration and compare the properties
    151         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
    152         for (Iterator i = conf.getKeys(); i.hasNext();)
    153         {
    154             String key = (String) i.next();
    155             assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key));
    156             assertEquals("Value of the '" + key + "' property", conf.getProperty(key), checkConfig.getProperty(key));
    157         }
    158
    159         // Save it again, verifing a save with a filename works.
    160         checkConfig.save();
    161     }
    162
    163     public void testInMemoryCreatedSave() throws Exception
    164     {
    165         // remove the file previously saved if necessary
    166         if (testSavePropertiesFile.exists())
    167         {
    168             assertTrue(testSavePropertiesFile.delete());
    169         }
    170
    171         PropertiesConfiguration pc = new PropertiesConfiguration();
    172         // add an array of strings to the configuration
    173         pc.addProperty("string", "value1");
    174         List list = new ArrayList();
    175         for (int i = 1; i < 5; i++)
    176         {
    177             list.add("value" + i);
    178         }
    179         pc.addProperty("array", list);
    180
    181         // save the configuration
    182         String filename = testSavePropertiesFile.getAbsolutePath();
    183         pc.save(filename);
    184
    185         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
    186
    187         // read the configuration and compare the properties
    188         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
    189         for (Iterator i = pc.getKeys(); i.hasNext();)
    190         {
    191             String key = (String) i.next();
    192             assertTrue("The saved configuration doesn't contain the key '" + key + "'",
    193                     checkConfig.containsKey(key));
    194             assertEquals("Value of the '" + key + "' property",
    195                     pc.getProperty(key), checkConfig.getProperty(key));
    196         }
    197
    198         // Save it again, verifing a save with a filename works.
    199         checkConfig.save();
    200     }
    201
    202     public void testSaveMissingFilename()
    203     {
    204         PropertiesConfiguration pc = new PropertiesConfiguration();
    205         try
    206         {
    207             pc.save();
    208             fail("Should have throw ConfigurationException");
    209         }
    210         catch (ConfigurationException ce)
    211         {
    212             //good
    213         }
    214     }
    215
    216     /***
    217      * Tests if the base path is taken into account by the save() method.
    218      * @throws Exception if an error occurs
    219      */
    220     public void testSaveWithBasePath() throws Exception
    221     {
    222         // remove the file previously saved if necessary
    223         if (testSavePropertiesFile.exists())
    224         {
    225             assertTrue(testSavePropertiesFile.delete());
    226         }
    227
    228         conf.setProperty("test", "true");
    229         conf.setBasePath(testSavePropertiesFile.getParentFile().toURL().toString());
    230         conf.setFileName(testSavePropertiesFile.getName());
    231         conf.save();
    232         assertTrue(testSavePropertiesFile.exists());
    233     }
    234
    235     public void testLoadViaProperty() throws Exception
    236     {
    237         PropertiesConfiguration pc = new PropertiesConfiguration();
    238         pc.setFileName(testProperties);
    239         pc.load();
    240
    241         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
    242     }
    243
    244     public void testLoadViaPropertyWithBasePath() throws Exception
    245     {
    246         PropertiesConfiguration pc = new PropertiesConfiguration();
    247         pc.setBasePath(testBasePath);
    248         pc.setFileName("test.properties");
    249         pc.load();
    250
    251         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
    252     }
    253
    254     public void testLoadViaPropertyWithBasePath2() throws Exception
    255     {
    256         PropertiesConfiguration pc = new PropertiesConfiguration();
    257         pc.setBasePath(testBasePath2);
    258         pc.setFileName("conf/test.properties");
    259         pc.load();
    260
    261         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
    262
    263         pc = new PropertiesConfiguration();
    264         pc.setBasePath(testBasePath2);
    265         pc.setFileName("conf/test.properties");
    266         pc.load();
    267
    268         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
    269     }
    270
    271     public void testLoadFromFile() throws Exception
    272     {
    273         File file = new File("conf/test.properties");
    274         conf = new PropertiesConfiguration(file);
    275
    276         assertEquals("true", conf.getString("configuration.loaded"));
    277     }
    278
    279     public void testLoadUnexistingFile()
    280     {
    281         try
    282         {
    283             conf = new PropertiesConfiguration("Unexisting file");
    284             fail("Unexisting file was loaded.");
    285         }
    286         catch(ConfigurationException cex)
    287         {
    288             // fine
    289         }
    290     }
    291
    292     /***
    293      * Tests to load a file with enabled auto save mode.
    294      */
    295     public void testLoadWithAutoSave() throws Exception
    296     {
    297         setUpSavedProperties();
    298     }
    299
    300     /***
    301      * Tests the auto save functionality when an existing property is modified.
    302      */
    303     public void testLoadWithAutoSaveAndSetExisting() throws Exception
    304     {
    305         setUpSavedProperties();
    306         conf.setProperty("a", "moreThanOne");
    307         checkSavedConfig();
    308     }
    309
    310     /***
    311      * Tests the auto save functionality when a new property is added using the
    312      * setProperty() method.
    313      */
    314     public void testLoadWithAutoSaveAndSetNew() throws Exception
    315     {
    316         setUpSavedProperties();
    317         conf.setProperty("d", "four");
    318         checkSavedConfig();
    319     }
    320
    321     /***
    322      * Tests the auto save functionality when a new property is added using the
    323      * addProperty() method.
    324      */
    325     public void testLoadWithAutoSaveAndAdd() throws Exception
    326     {
    327         setUpSavedProperties();
    328         conf.addProperty("d", "four");
    329         checkSavedConfig();
    330     }
    331
    332     /***
    333      * Tests the auto save functionality when a property is removed.
    334      */
    335     public void testLoadWithAutoSaveAndClear() throws Exception
    336     {
    337         setUpSavedProperties();
    338         conf.clearProperty("c");
    339         PropertiesConfiguration checkConfig = checkSavedConfig();
    340         assertFalse("The saved configuration contain the key '" + "c" + "'",
    341                 checkConfig.containsKey("c"));
    342     }
    343
    344     /***
    345      * Creates a properties file on disk. Used for testing load and save
    346      * operations.
    347      *
    348      * @throws IOException if an I/O error occurs
    349      */
    350     private void setUpSavedProperties() throws IOException,
    351             ConfigurationException
    352     {
    353         PrintWriter out = null;
    354
    355         try
    356         {
    357             out = new PrintWriter(new FileWriter(testSavePropertiesFile));
    358             out.println("a = one");
    359             out.println("b = two");
    360             out.println("c = three");
    361             out.close();
    362             out = null;
    363
    364             conf = new PropertiesConfiguration();
    365             conf.setAutoSave(true);
    366             conf.setFile(testSavePropertiesFile);
    367             conf.load();
    368             assertEquals("one", conf.getString("a"));
    369             assertEquals("two", conf.getString("b"));
    370             assertEquals("three", conf.getString("c"));
    371         }
    372         finally
    373         {
    374             if (out != null)
    375             {
    376                 out.close();
    377             }
    378         }
    379     }
    380
    381     /***
    382      * Helper method for testing a saved configuration. Reads in the file using
    383      * a new instance and compares this instance with the original one.
    384      *
    385      * @return the newly created configuration instance
    386      * @throws ConfigurationException if an error occurs
    387      */
    388     private PropertiesConfiguration checkSavedConfig()
    389             throws ConfigurationException
    390     {
    391         PropertiesConfiguration checkConfig = new PropertiesConfiguration(
    392                 testSavePropertiesFile);
    393         for (Iterator i = conf.getKeys(); i.hasNext();)
    394         {
    395             String key = (String) i.next();
    396             assertTrue("The saved configuration doesn't contain the key '"
    397                     + key + "'", checkConfig.containsKey(key));
    398             assertEquals("Value of the '" + key + "' property", conf
    399                     .getProperty(key), checkConfig.getProperty(key));
    400         }
    401         return checkConfig;
    402     }
    403
    404     public void testGetStringWithEscapedChars()
    405     {
    406         String property = conf.getString("test.unescape");
    407         assertEquals("String with escaped characters", "This \n string \t contains \" escaped // characters", property);
    408     }
    409
    410     public void testGetStringWithEscapedComma()
    411     {
    412         String property = conf.getString("test.unescape.list-separator");
    413         assertEquals("String with an escaped list separator", "This string contains , an escaped list separator", property);
    414     }
    415
    416     public void testUnescapeJava()
    417     {
    418         assertEquals("test//,test", PropertiesConfiguration.unescapeJava("test//,test", ','));
    419     }
    420
    421     public void testEscapedKey() throws Exception
    422     {
    423         PropertiesConfiguration conf = new PropertiesConfiguration();
    424         conf.load(new StringReader("//u0066//u006f//u006f=bar"));
    425
    426         assertEquals("value of the 'foo' property", "bar", conf.getString("foo"));
    427     }
    428
    429     public void testMixedArray()
    430     {
    431         String[] array = conf.getStringArray("test.mixed.array");
    432
    433         assertEquals("array length", 4, array.length);
    434         assertEquals("1st element", "a", array[0]);
    435         assertEquals("2nd element", "b", array[1]);
    436         assertEquals("3rd element", "c", array[2]);
    437         assertEquals("4th element", "d", array[3]);
    438     }
    439
    440     public void testMultilines()
    441     {
    442         String property = "This is a value spread out across several adjacent "
    443                 + "natural lines by escaping the line terminator with "
    444                 + "a backslash character.";
    445
    446         assertEquals("'test.multilines' property", property, conf.getString("test.multilines"));
    447     }
    448
    449     public void testChangingDefaultListDelimiter() throws Exception
    450     {
    451         PropertiesConfiguration pc = new PropertiesConfiguration(testProperties);
    452         assertEquals(4, pc.getList("test.mixed.array").size());
    453
    454         char delimiter = PropertiesConfiguration.getDefaultListDelimiter();
    455         PropertiesConfiguration.setDefaultListDelimiter('^');
    456         pc = new PropertiesConfiguration(testProperties);
    457         assertEquals(2, pc.getList("test.mixed.array").size());
    458         PropertiesConfiguration.setDefaultListDelimiter(delimiter);
    459     }
    460
    461     public void testChangingListDelimiter() throws Exception
    462     {
    463         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
    464         assertEquals(4, pc1.getList("test.mixed.array").size());
    465
    466         PropertiesConfiguration pc2 = new PropertiesConfiguration();
    467         pc2.setListDelimiter('^');
    468         pc2.setFileName(testProperties);
    469         pc2.load();
    470         assertEquals("Should obtain the first value", "a", pc2.getString("test.mixed.array"));
    471         assertEquals(2, pc2.getList("test.mixed.array").size());
    472     }
    473
    474     public void testDisableListDelimiter() throws Exception
    475     {
    476         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
    477         assertEquals(4, pc1.getList("test.mixed.array").size());
    478
    479         PropertiesConfiguration pc2 = new PropertiesConfiguration();
    480         pc2.setDelimiterParsingDisabled(true);
    481         pc2.setFileName(testProperties);
    482         pc2.load();
    483         assertEquals(2, pc2.getList("test.mixed.array").size());
    484     }
    485
    486     /***
    487      * Tests escaping of an end of line with a backslash.
    488      */
    489     public void testNewLineEscaping()
    490     {
    491         List list = conf.getList("test.path");
    492         assertEquals(3, list.size());
    493         assertEquals("C://path1//", list.get(0));
    494         assertEquals("C://path2//", list.get(1));
    495         assertEquals("C://path3//complex//test//", list.get(2));
    496     }
    497
    498     /***
    499      * Tests if included files are loaded when the source lies in the class path.
    500      */
    501     public void testLoadIncludeFromClassPath() throws ConfigurationException
    502     {
    503         conf = new PropertiesConfiguration("test.properties");
    504         assertEquals("true", conf.getString("include.loaded"));
    505     }
    506
    507     /***
    508      * Test if the lines starting with # or ! are properly ignored.
    509      */
    510     public void testComment() {
    511         assertFalse("comment line starting with '#' parsed as a property", conf.containsKey("#comment"));
    512         assertFalse("comment line starting with '!' parsed as a property", conf.containsKey("!comment"));
    513     }
    514
    515     /***
    516      * Check that key/value separators can be part of a key.
    517      */
    518     public void testEscapedKeyValueSeparator()
    519     {
    520         assertEquals("Escaped separator '=' not supported in keys", "foo", conf.getProperty("test.separator=in.key"));
    521         assertEquals("Escaped separator ':' not supported in keys", "bar", conf.getProperty("test.separator:in.key"));
    522         assertEquals("Escaped separator '//t' not supported in keys", "foo", conf.getProperty("test.separator\tin.key"));
    523         assertEquals("Escaped separator '//f' not supported in keys", "bar", conf.getProperty("test.separator\fin.key"));
    524         assertEquals("Escaped separator ' ' not supported in keys"  , "foo", conf.getProperty("test.separator in.key"));
    525     }
    526
    527     /***
    528      * Test all acceptable key/value separators ('=', ':' or white spaces).
    529      */
    530     public void testKeyValueSeparators() {
    531         assertEquals("equal separator not properly parsed",      "foo", conf.getProperty("test.separator.equal"));
    532         assertEquals("colon separator not properly parsed",      "foo", conf.getProperty("test.separator.colon"));
    533         assertEquals("tab separator not properly parsed",        "foo", conf.getProperty("test.separator.tab"));
    534         assertEquals("formfeed separator not properly parsed",   "foo", conf.getProperty("test.separator.formfeed"));
    535         assertEquals("whitespace separator not properly parsed", "foo", conf.getProperty("test.separator.whitespace"));
    536     }
    537
    538     /***
    539      * Tests including properties when they are loaded from a nested directory
    540      * structure.
    541      */
    542     public void testIncludeInSubDir() throws ConfigurationException
    543     {
    544         ConfigurationFactory factory = new ConfigurationFactory("conf/testFactoryPropertiesInclude.xml");
    545         Configuration config = factory.getConfiguration();
    546         assertEquals(true, config.getBoolean("deeptest"));
    547         assertEquals(true, config.getBoolean("deepinclude"));
    548         assertFalse(config.containsKey("deeptestinvalid"));
    549     }
    550
    551     /***
    552      * Tests whether the correct line separator is used.
    553      */
    554     public void testLineSeparator() throws ConfigurationException
    555     {
    556         final String EOL = System.getProperty("line.separator");
    557         conf = new PropertiesConfiguration();
    558         conf.setHeader("My header");
    559         conf.setProperty("prop", "value");
    560
    561         StringWriter out = new StringWriter();
    562         conf.save(out);
    563         String content = out.toString();
    564         assertTrue("Header could not be found", content.indexOf("# My header"
    565                 + EOL + EOL) == 0);
    566         assertTrue("Property could not be found", content
    567                 .indexOf("prop = value" + EOL) > 0);
    568     }
    569
    570     /***
    571      * Tests what happens if a reloading strategy's <code>reloadingRequired()</code>
    572      * implementation accesses methods of the configuration that in turn cause a reload.
    573      */
    574     public void testReentrantReload()
    575     {
    576         conf.setProperty("shouldReload", Boolean.FALSE);
    577         conf.setReloadingStrategy(new FileChangedReloadingStrategy()
    578         {
    579             public boolean reloadingRequired()
    580             {
    581                 return configuration.getBoolean("shouldReload");
    582             }
    583         });
    584         assertFalse("Property has wrong value", conf.getBoolean("shouldReload"));
    585     }
    586
    587     /***
    588      * Tests accessing the layout object.
    589      */
    590     public void testGetLayout()
    591     {
    592         PropertiesConfigurationLayout layout = conf.getLayout();
    593         assertNotNull("Layout is null", layout);
    594         assertSame("Different object returned", layout, conf.getLayout());
    595         conf.setLayout(null);
    596         PropertiesConfigurationLayout layout2 = conf.getLayout();
    597         assertNotNull("Layout 2 is null", layout2);
    598         assertNotSame("Same object returned", layout, layout2);
    599     }
    600
    601     /***
    602      * Tests the propertyLoaded() method for a simple property.
    603      */
    604     public void testPropertyLoaded() throws ConfigurationException
    605     {
    606         DummyLayout layout = new DummyLayout(conf);
    607         conf.setLayout(layout);
    608         conf.propertyLoaded("layoutLoadedProperty", "yes");
    609         assertEquals("Layout's load() was called", 0, layout.loadCalls);
    610         assertEquals("Property not added", "yes", conf
    611                 .getString("layoutLoadedProperty"));
    612     }
    613
    614     /***
    615      * Tests the propertyLoaded() method for an include property.
    616      */
    617     public void testPropertyLoadedInclude() throws ConfigurationException
    618     {
    619         DummyLayout layout = new DummyLayout(conf);
    620         conf.setLayout(layout);
    621         conf.propertyLoaded(PropertiesConfiguration.getInclude(),
    622                 "testClasspath.properties,testEqual.properties");
    623         assertEquals("Layout's load() was not correctly called", 2,
    624                 layout.loadCalls);
    625         assertFalse("Property was added", conf
    626                 .containsKey(PropertiesConfiguration.getInclude()));
    627     }
    628
    629     /***
    630      * Tests propertyLoaded() for an include property, when includes are
    631      * disabled.
    632      */
    633     public void testPropertyLoadedIncludeNotAllowed()
    634             throws ConfigurationException
    635     {
    636         DummyLayout layout = new DummyLayout(conf);
    637         conf.setLayout(layout);
    638         conf.setIncludesAllowed(false);
    639         conf.propertyLoaded(PropertiesConfiguration.getInclude(),
    640                 "testClassPath.properties,testEqual.properties");
    641         assertEquals("Layout's load() was called", 0, layout.loadCalls);
    642         assertFalse("Property was added", conf
    643                 .containsKey(PropertiesConfiguration.getInclude()));
    644     }
    645
    646     /***
    647      * Tests whether comment lines are correctly detected.
    648      */
    649     public void testIsCommentLine()
    650     {
    651         assertTrue("Comment not detected", PropertiesConfiguration
    652                 .isCommentLine("# a comment"));
    653         assertTrue("Alternative comment not detected", PropertiesConfiguration
    654                 .isCommentLine("! a comment"));
    655         assertTrue("Comment with no space not detected",
    656                 PropertiesConfiguration.isCommentLine("#a comment"));
    657         assertTrue("Comment with leading space not detected",
    658                 PropertiesConfiguration.isCommentLine("    ! a comment"));
    659         assertFalse("Wrong comment", PropertiesConfiguration
    660                 .isCommentLine("   a#comment"));
    661     }
    662
    663     /***
    664      * Tests whether a properties configuration can be successfully cloned. It
    665      * is especially checked whether the layout object is taken into account.
    666      */
    667     public void testClone() throws ConfigurationException
    668     {
    669         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
    670         assertNotSame("Copy has same layout object", conf.getLayout(), copy
    671                 .getLayout());
    672         assertEquals("Wrong number of event listeners for original", 1, conf
    673                 .getConfigurationListeners().size());
    674         assertEquals("Wrong number of event listeners for clone", 1, copy
    675                 .getConfigurationListeners().size());
    676         assertSame("Wrong event listener for original", conf.getLayout(), conf
    677                 .getConfigurationListeners().iterator().next());
    678         assertSame("Wrong event listener for clone", copy.getLayout(), copy
    679                 .getConfigurationListeners().iterator().next());
    680         StringWriter outConf = new StringWriter();
    681         conf.save(outConf);
    682         StringWriter outCopy = new StringWriter();
    683         copy.save(outCopy);
    684         assertEquals("Output from copy is different", outConf.toString(),
    685                 outCopy.toString());
    686     }
    687
    688     /***
    689      * Tests the clone() method when no layout object exists yet.
    690      */
    691     public void testCloneNullLayout()
    692     {
    693         conf = new PropertiesConfiguration();
    694         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
    695         assertNotSame("Layout objects are the same", conf.getLayout(), copy
    696                 .getLayout());
    697     }
    698
    699     /***
    700      * A dummy layout implementation for checking whether certain methods are
    701      * correctly called by the configuration.
    702      */
    703     static class DummyLayout extends PropertiesConfigurationLayout
    704     {
    705         /*** Stores the number how often load() was called. */
    706         public int loadCalls;
    707
    708         public DummyLayout(PropertiesConfiguration config)
    709         {
    710             super(config);
    711         }
    712
    713         public void load(Reader in) throws ConfigurationException
    714         {
    715             loadCalls++;
    716         }
    717     }
    718 }