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 }