    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  package org.apache.commons.configuration;
    18
    19  import java.io.File;
    20  import java.io.FileWriter;
    21  import java.io.IOException;
    22  import java.io.PrintWriter;
    23  import java.text.MessageFormat;
    24  import java.util.ArrayList;
    25  import java.util.Collection;
    26  import java.util.Iterator;
    27  import java.util.NoSuchElementException;
    28  import java.util.Set;
    29
    30  import junit.framework.Assert;
    31  import junit.framework.TestCase;
    32
    33  import org.apache.commons.configuration.event.ConfigurationEvent;
    34  import org.apache.commons.configuration.event.ConfigurationListener;
    35  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
    36  import org.apache.commons.configuration.tree.DefaultExpressionEngine;
    37  import org.apache.commons.configuration.tree.NodeCombiner;
    38  import org.apache.commons.configuration.tree.UnionCombiner;
    39
    40  /***
    41   * Test class for CombinedConfiguration.
    42   *
    43   * @version $Id: TestCombinedConfiguration.java 712401 2008-11-08 15:29:56Z oheger $
    44   */
    45  public class TestCombinedConfiguration extends TestCase
    46  {
    47      /*** Constant for the name of a sub configuration. */
    48      private static final String TEST_NAME = "SUBCONFIG";
    49
    50      /*** Constant for a test key. */
    51      private static final String TEST_KEY = "test.value";
    52
    53      /*** Constant for the name of the first child configuration.*/
    54      private static final String CHILD1 = TEST_NAME + "1";
    55
    56      /*** Constant for the name of the second child configuration.*/
    57      private static final String CHILD2 = TEST_NAME + "2";
    58
    59      /*** Constant for the name of the XML reload test file.*/
    60      private static final String RELOAD_XML_NAME = "reload.xml";
    61
    62      /*** Constant for the content of a XML reload test file.*/
    63      private static final String RELOAD_XML_CONTENT = "<xml><xmlReload>{0}</xmlReload></xml>";
    64
    65      /*** Constant for the name of the properties reload test file.*/
    66      private static final String RELOAD_PROPS_NAME = "reload.properties";
    67
    68      /*** Constant for the content of a properties reload test file.*/
    69      private static final String RELOAD_PROPS_CONTENT = "propsReload = {0}";
    70
    71      /*** Constant for the directory for writing test files.*/
    72      private static final File TEST_DIR = new File("target");
    73
    74      /*** A list with files created during a test.*/
    75      private Collection testFiles;
    76
    77      /*** The configuration to be tested. */
    78      private CombinedConfiguration config;
    79
    80      /*** The test event listener. */
    81      private CombinedListener listener;
    82
    83      protected void setUp() throws Exception
    84      {
    85          super.setUp();
    86          config = new CombinedConfiguration();
    87          listener = new CombinedListener();
    88          config.addConfigurationListener(listener);
    89      }
    90
    91      /***
    92       * Performs clean-up after a test run. If test files have been created, they
    93       * are removed now.
    94       */
    95      protected void tearDown() throws Exception
    96      {
    97          if (testFiles != null)
    98          {
    99              for (Iterator it = testFiles.iterator(); it.hasNext();)
    100             {
    101                 File f = (File) it.next();
    102                 if (f.exists())
    103                 {
    104                     assertTrue("Cannot delete test file: " + f, f.delete());
    105                 }
    106             }
    107         }
    108     }
    109
    110     /***
    111      * Tests accessing a newly created combined configuration.
    112      */
    113     public void testInit()
    114     {
    115         assertEquals("Already configurations contained", 0, config
    116                 .getNumberOfConfigurations());
    117         assertTrue("Set of names is not empty", config.getConfigurationNames()
    118                 .isEmpty());
    119         assertTrue("Wrong node combiner",
    120                 config.getNodeCombiner() instanceof UnionCombiner);
    121         assertNull("Test config was found", config.getConfiguration(TEST_NAME));
    122         assertFalse("Force reload check flag is set", config.isForceReloadCheck());
    123     }
    124
    125     /***
    126      * Tests adding a configuration (without further information).
    127      */
    128     public void testAddConfiguration()
    129     {
    130         AbstractConfiguration c = setUpTestConfiguration();
    131         config.addConfiguration(c);
    132         checkAddConfig(c);
    133         assertEquals("Wrong number of configs", 1, config
    134                 .getNumberOfConfigurations());
    135         assertTrue("Name list is not empty", config.getConfigurationNames()
    136                 .isEmpty());
    137         assertSame("Added config not found", c, config.getConfiguration(0));
    138         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
    139         listener.checkEvent(1, 0);
    140     }
    141
    142     /***
    143      * Tests adding a configuration with a name.
    144      */
    145     public void testAddConfigurationWithName()
    146     {
    147         AbstractConfiguration c = setUpTestConfiguration();
    148         config.addConfiguration(c, TEST_NAME);
    149         checkAddConfig(c);
    150         assertEquals("Wrong number of configs", 1, config
    151                 .getNumberOfConfigurations());
    152         assertSame("Added config not found", c, config.getConfiguration(0));
    153         assertSame("Added config not found by name", c, config
    154                 .getConfiguration(TEST_NAME));
    155         Set names = config.getConfigurationNames();
    156         assertEquals("Wrong number of config names", 1, names.size());
    157         assertTrue("Name not found", names.contains(TEST_NAME));
    158         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
    159         listener.checkEvent(1, 0);
    160     }
    161
    162     /***
    163      * Tests adding a configuration with a name when this name already exists.
    164      * This should cause an exception.
    165      */
    166     public void testAddConfigurationWithNameTwice()
    167     {
    168         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
    169         try
    170         {
    171             config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
    172                     "prefix");
    173             fail("Could add config with same name!");
    174         }
    175         catch (ConfigurationRuntimeException cex)
    176         {
    177             // ok
    178         }
    179     }
    180
    181     /***
    182      * Tests adding a configuration and specifying an at position.
    183      */
    184     public void testAddConfigurationAt()
    185     {
    186         AbstractConfiguration c = setUpTestConfiguration();
    187         config.addConfiguration(c, null, "my");
    188         checkAddConfig(c);
    189         assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
    190     }
    191
    192     /***
    193      * Tests adding a configuration with a complex at position. Here the at path
    194      * contains a dot, which must be escaped.
    195      */
    196     public void testAddConfigurationComplexAt()
    197     {
    198         AbstractConfiguration c = setUpTestConfiguration();
    199         config.addConfiguration(c, null, "This..is.a.complex");
    200         checkAddConfig(c);
    201         assertTrue("Wrong property value", config
    202                 .getBoolean("This..is.a.complex." + TEST_KEY));
    203     }
    204
    205     /***
    206      * Checks if a configuration was correctly added to the combined config.
    207      *
    208      * @param c the config to check
    209      */
    210     private void checkAddConfig(AbstractConfiguration c)
    211     {
    212         Collection listeners = c.getConfigurationListeners();
    213         assertEquals("Wrong number of configuration listeners", 1, listeners
    214                 .size());
    215         assertTrue("Combined config is no listener", listeners.contains(config));
    216     }
    217
    218     /***
    219      * Tests adding a null configuration. This should cause an exception to be
    220      * thrown.
    221      */
    222     public void testAddNullConfiguration()
    223     {
    224         try
    225         {
    226             config.addConfiguration(null);
    227             fail("Could add null configuration!");
    228         }
    229         catch (IllegalArgumentException iex)
    230         {
    231             // ok
    232         }
    233     }
    234
    235     /***
    236      * Tests accessing properties if no configurations have been added.
    237      */
    238     public void testAccessPropertyEmpty()
    239     {
    240         assertFalse("Found a key", config.containsKey(TEST_KEY));
    241         assertNull("Key has a value", config.getString("test.comment"));
    242         assertTrue("Config is not empty", config.isEmpty());
    243     }
    244
    245     /***
    246      * Tests accessing properties if multiple configurations have been added.
    247      */
    248     public void testAccessPropertyMulti()
    249     {
    250         config.addConfiguration(setUpTestConfiguration());
    251         config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
    252         config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
    253         assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
    254         assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
    255         assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
    256         assertFalse("Configuration is empty", config.isEmpty());
    257         listener.checkEvent(3, 0);
    258     }
    259
    260     /***
    261      * Tests removing a configuration.
    262      */
    263     public void testRemoveConfiguration()
    264     {
    265         AbstractConfiguration c = setUpTestConfiguration();
    266         config.addConfiguration(c);
    267         checkAddConfig(c);
    268         assertTrue("Config could not be removed", config.removeConfiguration(c));
    269         checkRemoveConfig(c);
    270     }
    271
    272     /***
    273      * Tests removing a configuration by index.
    274      */
    275     public void testRemoveConfigurationAt()
    276     {
    277         AbstractConfiguration c = setUpTestConfiguration();
    278         config.addConfiguration(c);
    279         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
    280         checkRemoveConfig(c);
    281     }
    282
    283     /***
    284      * Tests removing a configuration by name.
    285      */
    286     public void testRemoveConfigurationByName()
    287     {
    288         AbstractConfiguration c = setUpTestConfiguration();
    289         config.addConfiguration(c, TEST_NAME);
    290         assertSame("Wrong config removed", c, config
    291                 .removeConfiguration(TEST_NAME));
    292         checkRemoveConfig(c);
    293     }
    294
    295     /***
    296      * Tests removing a configuration with a name.
    297      */
    298     public void testRemoveNamedConfiguration()
    299     {
    300         AbstractConfiguration c = setUpTestConfiguration();
    301         config.addConfiguration(c, TEST_NAME);
    302         config.removeConfiguration(c);
    303         checkRemoveConfig(c);
    304     }
    305
    306     /***
    307      * Tests removing a named configuration by index.
    308      */
    309     public void testRemoveNamedConfigurationAt()
    310     {
    311         AbstractConfiguration c = setUpTestConfiguration();
    312         config.addConfiguration(c, TEST_NAME);
    313         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
    314         checkRemoveConfig(c);
    315     }
    316
    317     /***
    318      * Tests removing a configuration that was not added prior.
    319      */
    320     public void testRemoveNonContainedConfiguration()
    321     {
    322         assertFalse("Could remove non contained config", config
    323                 .removeConfiguration(setUpTestConfiguration()));
    324         listener.checkEvent(0, 0);
    325     }
    326
    327     /***
    328      * Tests removing a configuration by name, which is not contained.
    329      */
    330     public void testRemoveConfigurationByUnknownName()
    331     {
    332         assertNull("Could remove configuration by unknown name", config
    333                 .removeConfiguration("unknownName"));
    334         listener.checkEvent(0, 0);
    335     }
    336
    337     /***
    338      * Tests whether a configuration was completely removed.
    339      *
    340      * @param c the removed configuration
    341      */
    342     private void checkRemoveConfig(AbstractConfiguration c)
    343     {
    344         assertTrue("Listener was not removed", c.getConfigurationListeners()
    345                 .isEmpty());
    346         assertEquals("Wrong number of contained configs", 0, config
    347                 .getNumberOfConfigurations());
    348         assertTrue("Name was not removed", config.getConfigurationNames()
    349                 .isEmpty());
    350         listener.checkEvent(2, 0);
    351     }
    352
    353     /***
    354      * Tests if an update of a contained configuration leeds to an invalidation
    355      * of the combined configuration.
    356      */
    357     public void testUpdateContainedConfiguration()
    358     {
    359         AbstractConfiguration c = setUpTestConfiguration();
    360         config.addConfiguration(c);
    361         c.addProperty("test.otherTest", "yes");
    362         assertEquals("New property not found", "yes", config
    363                 .getString("test.otherTest"));
    364         listener.checkEvent(2, 0);
    365     }
    366
    367     /***
    368      * Tests if setting a node combiner causes an invalidation.
    369      */
    370     public void testSetNodeCombiner()
    371     {
    372         NodeCombiner combiner = new UnionCombiner();
    373         config.setNodeCombiner(combiner);
    374         assertSame("Node combiner was not set", combiner, config
    375                 .getNodeCombiner());
    376         listener.checkEvent(1, 0);
    377     }
    378
    379     /***
    380      * Tests setting a null node combiner. This should cause an exception.
    381      */
    382     public void testSetNullNodeCombiner()
    383     {
    384         try
    385         {
    386             config.setNodeCombiner(null);
    387             fail("Could set null node combiner!");
    388         }
    389         catch (IllegalArgumentException iex)
    390         {
    391             // ok
    392         }
    393     }
    394
    395     /***
    396      * Tests cloning a combined configuration.
    397      */
    398     public void testClone()
    399     {
    400         config.addConfiguration(setUpTestConfiguration());
    401         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
    402         config.addConfiguration(new PropertiesConfiguration(), "props");
    403
    404         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
    405         assertEquals("Wrong number of contained configurations", config
    406                 .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
    407         assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
    408                 .getNodeCombiner());
    409         assertEquals("Wrong number of names", config.getConfigurationNames()
    410                 .size(), cc2.getConfigurationNames().size());
    411         assertTrue("Event listeners were cloned", cc2
    412                 .getConfigurationListeners().isEmpty());
    413
    414         StrictConfigurationComparator comp = new StrictConfigurationComparator();
    415         for (int i = 0; i < config.getNumberOfConfigurations(); i++)
    416         {
    417             assertNotSame("Configuration at " + i + " was not cloned", config
    418                     .getConfiguration(i), cc2.getConfiguration(i));
    419             assertEquals("Wrong config class at " + i, config.getConfiguration(
    420                     i).getClass(), cc2.getConfiguration(i).getClass());
    421             assertTrue("Configs not equal at " + i, comp.compare(config
    422                     .getConfiguration(i), cc2.getConfiguration(i)));
    423         }
    424
    425         assertTrue("Combined configs not equal", comp.compare(config, cc2));
    426     }
    427
    428     /***
    429      * Tests if the cloned configuration is decoupled from the original.
    430      */
    431     public void testCloneModify()
    432     {
    433         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
    434         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
    435         assertTrue("Name is missing", cc2.getConfigurationNames().contains(
    436                 TEST_NAME));
    437         cc2.removeConfiguration(TEST_NAME);
    438         assertFalse("Names in original changed", config.getConfigurationNames()
    439                 .isEmpty());
    440     }
    441
    442     /***
    443      * Tests clearing a combined configuration. This should remove all contained
    444      * configurations.
    445      */
    446     public void testClear()
    447     {
    448         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
    449         config.addConfiguration(setUpTestConfiguration());
    450
    451         config.clear();
    452         assertEquals("Still configs contained", 0, config
    453                 .getNumberOfConfigurations());
    454         assertTrue("Still names contained", config.getConfigurationNames()
    455                 .isEmpty());
    456         assertTrue("Config is not empty", config.isEmpty());
    457
    458         listener.checkEvent(3, 2);
    459     }
    460
    461     /***
    462      * Tests if file-based configurations can be reloaded.
    463      */
    464     public void testReloading() throws Exception
    465     {
    466         config.setForceReloadCheck(true);
    467         File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
    468         File testPropsFile = writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
    469         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
    470         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    471         PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
    472         c2.setThrowExceptionOnMissing(true);
    473         c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    474         config.addConfiguration(c1);
    475         config.addConfiguration(c2);
    476         assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
    477         assertEquals("Wrong props reload value", 0, config
    478                 .getInt("propsReload"));
    479
    480         writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
    481         assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
    482         config.setForceReloadCheck(false);
    483         writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 1);
    484         assertEquals("Props reload detected though check flag is false", 0, config
    485                 .getInt("propsReload"));
    486     }
    487
    488     /***
    489      * Tests whether the reload check works with a subnode configuration. This
    490      * test is related to CONFIGURATION-341.
    491      */
    492     public void testReloadingSubnodeConfig() throws IOException,
    493             ConfigurationException
    494     {
    495         config.setForceReloadCheck(true);
    496         File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT,
    497                 0);
    498         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
    499         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    500         final String prefix = "reloadCheck";
    501         config.addConfiguration(c1, CHILD1, prefix);
    502         SubnodeConfiguration sub = config.configurationAt(prefix, true);
    503         writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
    504         assertEquals("Reload not detected", 1, sub.getInt("xmlReload"));
    505     }
    506
    507     /***
    508      * Prepares a test of the getSource() method.
    509      */
    510     private void setUpSourceTest()
    511     {
    512         HierarchicalConfiguration c1 = new HierarchicalConfiguration();
    513         PropertiesConfiguration c2 = new PropertiesConfiguration();
    514         c1.addProperty(TEST_KEY, TEST_NAME);
    515         c2.addProperty("another.key", "test");
    516         config.addConfiguration(c1, CHILD1);
    517         config.addConfiguration(c2, CHILD2);
    518     }
    519
    520     /***
    521      * Tests the gestSource() method when the source property is defined in a
    522      * hierarchical configuration.
    523      */
    524     public void testGetSourceHierarchical()
    525     {
    526         setUpSourceTest();
    527         assertEquals("Wrong source configuration", config
    528                 .getConfiguration(CHILD1), config.getSource(TEST_KEY));
    529     }
    530
    531     /***
    532      * Tests whether the source configuration can be detected for non
    533      * hierarchical configurations.
    534      */
    535     public void testGetSourceNonHierarchical()
    536     {
    537         setUpSourceTest();
    538         assertEquals("Wrong source configuration", config
    539                 .getConfiguration(CHILD2), config.getSource("another.key"));
    540     }
    541
    542     /***
    543      * Tests the getSource() method when the passed in key is not contained.
    544      * Result should be null in this case.
    545      */
    546     public void testGetSourceUnknown()
    547     {
    548         setUpSourceTest();
    549         assertNull("Wrong result for unknown key", config
    550                 .getSource("an.unknown.key"));
    551     }
    552
    553     /***
    554      * Tests the getSource() method when a null key is passed in. This should
    555      * cause an exception.
    556      */
    557     public void testGetSourceNull()
    558     {
    559         try
    560         {
    561             config.getSource(null);
    562             fail("Could resolve source for null key!");
    563         }
    564         catch (IllegalArgumentException iex)
    565         {
    566             // ok
    567         }
    568     }
    569
    570     /***
    571      * Tests the getSource() method when the passed in key belongs to the
    572      * combined configuration itself.
    573      */
    574     public void testGetSourceCombined()
    575     {
    576         setUpSourceTest();
    577         final String key = "yet.another.key";
    578         config.addProperty(key, Boolean.TRUE);
    579         assertEquals("Wrong source for key", config, config.getSource(key));
    580     }
    581
    582     /***
    583      * Tests the getSource() method when the passed in key refers to multiple
    584      * values, which are all defined in the same source configuration.
    585      */
    586     public void testGetSourceMulti()
    587     {
    588         setUpSourceTest();
    589         final String key = "list.key";
    590         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
    591         assertEquals("Wrong source for multi-value property", config
    592                 .getConfiguration(CHILD1), config.getSource(key));
    593     }
    594
    595     /***
    596      * Tests the getSource() method when the passed in key refers to multiple
    597      * values defined by different sources. This should cause an exception.
    598      */
    599     public void testGetSourceMultiSources()
    600     {
    601         setUpSourceTest();
    602         final String key = "list.key";
    603         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
    604         config.getConfiguration(CHILD2).addProperty(key, "a,b,c");
    605         try
    606         {
    607             config.getSource(key);
    608             fail("Multiple sources not detected!");
    609         }
    610         catch (IllegalArgumentException iex)
    611         {
    612             //ok
    613         }
    614     }
    615
    616     /***
    617      * Tests whether escaped list delimiters are treated correctly.
    618      */
    619     public void testEscapeListDelimiters()
    620     {
    621         PropertiesConfiguration sub = new PropertiesConfiguration();
    622         sub.addProperty("test.pi", "3//,1415");
    623         config.addConfiguration(sub);
    624         assertEquals("Wrong value", "3,1415", config.getString("test.pi"));
    625     }
    626
    627     /***
    628      * Tests whether an invalidate event is fired only after a change. This test
    629      * is related to CONFIGURATION-315.
    630      */
    631     public void testInvalidateAfterChange()
    632     {
    633         ConfigurationEvent event = new ConfigurationEvent(config, 0, null,
    634                 null, true);
    635         config.configurationChanged(event);
    636         assertEquals("Invalidate event fired", 0, listener.invalidateEvents);
    637         event = new ConfigurationEvent(config, 0, null, null, false);
    638         config.configurationChanged(event);
    639         assertEquals("No invalidate event fired", 1, listener.invalidateEvents);
    640     }
    641
    642     /***
    643      * Tests using a conversion expression engine for child configurations with
    644      * strange keys. This test is related to CONFIGURATION-336.
    645      */
    646     public void testConversionExpressionEngine()
    647     {
    648         PropertiesConfiguration child = new PropertiesConfiguration();
    649         child.addProperty("test(a)", "1,2,3");
    650         config.addConfiguration(child);
    651         DefaultExpressionEngine engineQuery = new DefaultExpressionEngine();
    652         engineQuery.setIndexStart("<");
    653         engineQuery.setIndexEnd(">");
    654         config.setExpressionEngine(engineQuery);
    655         DefaultExpressionEngine engineConvert = new DefaultExpressionEngine();
    656         engineConvert.setIndexStart("[");
    657         engineConvert.setIndexEnd("]");
    658         config.setConversionExpressionEngine(engineConvert);
    659         assertEquals("Wrong property 1", "1", config.getString("test(a)<0>"));
    660         assertEquals("Wrong property 2", "2", config.getString("test(a)<1>"));
    661         assertEquals("Wrong property 3", "3", config.getString("test(a)<2>"));
    662     }
    663
    664     /***
    665      * Tests whether reload operations can cause a deadlock when the combined
    666      * configuration is accessed concurrently. This test is related to
    667      * CONFIGURATION-344.
    668      */
    669     public void testDeadlockWithReload() throws ConfigurationException,
    670             InterruptedException
    671     {
    672         final PropertiesConfiguration child = new PropertiesConfiguration(
    673                 "test.properties");
    674         child.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    675         config.addConfiguration(child);
    676         final int count = 1000;
    677
    678         class ReloadThread extends Thread
    679         {
    680             boolean error = false;
    681
    682             public void run()
    683             {
    684                 for (int i = 0; i < count && !error; i++)
    685                 {
    686                     try
    687                     {
    688                         if (!child.getBoolean("configuration.loaded"))
    689                         {
    690                             error = true;
    691                         }
    692                     }
    693                     catch (NoSuchElementException nsex)
    694                     {
    695                         error = true;
    696                     }
    697                 }
    698             }
    699         }
    700
    701         ReloadThread reloadThread = new ReloadThread();
    702         reloadThread.start();
    703         for (int i = 0; i < count; i++)
    704         {
    705             assertEquals("Wrong value of combined property", 10, config
    706                     .getInt("test.integer"));
    707         }
    708         reloadThread.join();
    709         assertFalse("Failure in thread", reloadThread.error);
    710     }
    711
    712     /***
    713      * Helper method for writing a file. The file is also added to a list and
    714      * will be deleted in teadDown() automatically.
    715      *
    716      * @param file the file to be written
    717      * @param content the file's content
    718      * @throws IOException if an error occurs
    719      */
    720     private void writeFile(File file, String content) throws IOException
    721     {
    722         PrintWriter out = null;
    723         try
    724         {
    725             out = new PrintWriter(new FileWriter(file));
    726             out.print(content);
    727
    728             if (testFiles == null)
    729             {
    730                 testFiles = new ArrayList();
    731             }
    732             testFiles.add(file);
    733         }
    734         finally
    735         {
    736             if (out != null)
    737             {
    738                 out.close();
    739             }
    740         }
    741     }
    742
    743     /***
    744      * Helper method for writing a test file. The file will be created in the
    745      * test directory. It is also scheduled for automatic deletion after the
    746      * test.
    747      *
    748      * @param fileName the name of the test file
    749      * @param content the content of the file
    750      * @return the <code>File</code> object for the test file
    751      * @throws IOException if an error occurs
    752      */
    753     private File writeFile(String fileName, String content) throws IOException
    754     {
    755         File file = new File(TEST_DIR, fileName);
    756         writeFile(file, content);
    757         return file;
    758     }
    759
    760     /***
    761      * Writes a file for testing reload operations.
    762      *
    763      * @param name the name of the reload test file
    764      * @param content the content of the file
    765      * @param value the value of the reload test property
    766      * @return the file that was written
    767      * @throws IOException if an error occurs
    768      */
    769     private File writeReloadFile(String name, String content, int value)
    770             throws IOException
    771     {
    772         return writeFile(name, MessageFormat.format(content, new Object[] {
    773             new Integer(value)
    774         }));
    775     }
    776
    777     /***
    778      * Helper method for creating a test configuration to be added to the
    779      * combined configuration.
    780      *
    781      * @return the test configuration
    782      */
    783     private AbstractConfiguration setUpTestConfiguration()
    784     {
    785         HierarchicalConfiguration config = new HierarchicalConfiguration();
    786         config.addProperty(TEST_KEY, Boolean.TRUE);
    787         config.addProperty("test.comment", "This is a test");
    788         return config;
    789     }
    790
    791     /***
    792      * Test event listener class for checking if the expected invalidate events
    793      * are fired.
    794      */
    795     static class CombinedListener implements ConfigurationListener
    796     {
    797         int invalidateEvents;
    798
    799         int otherEvents;
    800
    801         public void configurationChanged(ConfigurationEvent event)
    802         {
    803             if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
    804             {
    805                 invalidateEvents++;
    806             }
    807             else
    808             {
    809                 otherEvents++;
    810             }
    811         }
    812
    813         /***
    814          * Checks if the expected number of events was fired.
    815          *
    816          * @param expectedInvalidate the expected number of invalidate events
    817          * @param expectedOthers the expected number of other events
    818          */
    819         public void checkEvent(int expectedInvalidate, int expectedOthers)
    820         {
    821             Assert.assertEquals("Wrong number of invalidate events",
    822                     expectedInvalidate, invalidateEvents);
    823             Assert.assertEquals("Wrong number of other events", expectedOthers,
    824                     otherEvents);
    825         }
    826     }
    827 }