001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration;
018    
019    import static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertFalse;
021    import static org.junit.Assert.assertNotNull;
022    import static org.junit.Assert.assertNotSame;
023    import static org.junit.Assert.assertNull;
024    import static org.junit.Assert.assertSame;
025    import static org.junit.Assert.assertTrue;
026    
027    import java.io.File;
028    import java.io.FileWriter;
029    import java.io.IOException;
030    import java.io.PrintWriter;
031    import java.io.StringReader;
032    import java.io.StringWriter;
033    import java.text.MessageFormat;
034    import java.util.Collection;
035    import java.util.List;
036    import java.util.NoSuchElementException;
037    import java.util.Set;
038    
039    import junit.framework.Assert;
040    
041    import org.apache.commons.configuration.event.ConfigurationEvent;
042    import org.apache.commons.configuration.event.ConfigurationListener;
043    import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
044    import org.apache.commons.configuration.reloading.FileRandomReloadingStrategy;
045    import org.apache.commons.configuration.tree.DefaultExpressionEngine;
046    import org.apache.commons.configuration.tree.MergeCombiner;
047    import org.apache.commons.configuration.tree.NodeCombiner;
048    import org.apache.commons.configuration.tree.OverrideCombiner;
049    import org.apache.commons.configuration.tree.UnionCombiner;
050    import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
051    import org.junit.Before;
052    import org.junit.Rule;
053    import org.junit.Test;
054    import org.junit.rules.TemporaryFolder;
055    
056    /**
057     * Test class for CombinedConfiguration.
058     *
059     * @version $Id: TestCombinedConfiguration.java 1327061 2012-04-17 12:18:27Z rgoers $
060     */
061    public class TestCombinedConfiguration
062    {
063        /** Constant for the name of a sub configuration. */
064        private static final String TEST_NAME = "SUBCONFIG";
065    
066        /** Constant for a test key. */
067        private static final String TEST_KEY = "test.value";
068    
069        /** Constant for the name of the first child configuration.*/
070        private static final String CHILD1 = TEST_NAME + "1";
071    
072        /** Constant for the name of the second child configuration.*/
073        private static final String CHILD2 = TEST_NAME + "2";
074    
075        /** Constant for the name of the XML reload test file.*/
076        private static final String RELOAD_XML_NAME = "reload.xml";
077    
078        /** Constant for the content of a XML reload test file.*/
079        private static final String RELOAD_XML_CONTENT = "<xml><xmlReload>{0}</xmlReload></xml>";
080    
081        /** Constant for the name of the properties reload test file.*/
082        private static final String RELOAD_PROPS_NAME = "reload.properties";
083    
084        /** Constant for the content of a properties reload test file.*/
085        private static final String RELOAD_PROPS_CONTENT = "propsReload = {0}";
086    
087        /** Helper object for managing temporary files. */
088        @Rule
089        public TemporaryFolder folder = new TemporaryFolder();
090    
091        /** The configuration to be tested. */
092        private CombinedConfiguration config;
093    
094        /** The test event listener. */
095        private CombinedListener listener;
096    
097        @Before
098        public void setUp() throws Exception
099        {
100            config = new CombinedConfiguration();
101            listener = new CombinedListener();
102            config.addConfigurationListener(listener);
103        }
104    
105        /**
106         * Tests accessing a newly created combined configuration.
107         */
108        @Test
109        public void testInit()
110        {
111            assertEquals("Already configurations contained", 0, config
112                    .getNumberOfConfigurations());
113            assertTrue("Set of names is not empty", config.getConfigurationNames()
114                    .isEmpty());
115            assertTrue("Wrong node combiner",
116                    config.getNodeCombiner() instanceof UnionCombiner);
117            assertNull("Test config was found", config.getConfiguration(TEST_NAME));
118            assertFalse("Force reload check flag is set", config.isForceReloadCheck());
119        }
120    
121        /**
122         * Tests adding a configuration (without further information).
123         */
124        @Test
125        public void testAddConfiguration()
126        {
127            AbstractConfiguration c = setUpTestConfiguration();
128            config.addConfiguration(c);
129            checkAddConfig(c);
130            assertEquals("Wrong number of configs", 1, config
131                    .getNumberOfConfigurations());
132            assertTrue("Name list is not empty", config.getConfigurationNames()
133                    .isEmpty());
134            assertSame("Added config not found", c, config.getConfiguration(0));
135            assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
136            listener.checkEvent(1, 0);
137        }
138    
139        /**
140         * Tests adding a configuration with a name.
141         */
142        @Test
143        public void testAddConfigurationWithName()
144        {
145            AbstractConfiguration c = setUpTestConfiguration();
146            config.addConfiguration(c, TEST_NAME);
147            checkAddConfig(c);
148            assertEquals("Wrong number of configs", 1, config
149                    .getNumberOfConfigurations());
150            assertSame("Added config not found", c, config.getConfiguration(0));
151            assertSame("Added config not found by name", c, config
152                    .getConfiguration(TEST_NAME));
153            Set<String> names = config.getConfigurationNames();
154            assertEquals("Wrong number of config names", 1, names.size());
155            assertTrue("Name not found", names.contains(TEST_NAME));
156            assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
157            listener.checkEvent(1, 0);
158        }
159    
160        /**
161         * Tests adding a configuration with a name when this name already exists.
162         * This should cause an exception.
163         */
164        @Test(expected = ConfigurationRuntimeException.class)
165        public void testAddConfigurationWithNameTwice()
166        {
167            config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
168            config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
169                    "prefix");
170        }
171    
172        /**
173         * Tests adding a configuration and specifying an at position.
174         */
175        @Test
176        public void testAddConfigurationAt()
177        {
178            AbstractConfiguration c = setUpTestConfiguration();
179            config.addConfiguration(c, null, "my");
180            checkAddConfig(c);
181            assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
182        }
183    
184        /**
185         * Tests adding a configuration with a complex at position. Here the at path
186         * contains a dot, which must be escaped.
187         */
188        @Test
189        public void testAddConfigurationComplexAt()
190        {
191            AbstractConfiguration c = setUpTestConfiguration();
192            config.addConfiguration(c, null, "This..is.a.complex");
193            checkAddConfig(c);
194            assertTrue("Wrong property value", config
195                    .getBoolean("This..is.a.complex." + TEST_KEY));
196        }
197    
198        /**
199         * Checks if a configuration was correctly added to the combined config.
200         *
201         * @param c the config to check
202         */
203        private void checkAddConfig(AbstractConfiguration c)
204        {
205            Collection<ConfigurationListener> listeners = c.getConfigurationListeners();
206            assertEquals("Wrong number of configuration listeners", 1, listeners
207                    .size());
208            assertTrue("Combined config is no listener", listeners.contains(config));
209        }
210    
211        /**
212         * Tests adding a null configuration. This should cause an exception to be
213         * thrown.
214         */
215        @Test(expected = IllegalArgumentException.class)
216        public void testAddNullConfiguration()
217        {
218            config.addConfiguration(null);
219        }
220    
221        /**
222         * Tests accessing properties if no configurations have been added.
223         */
224        @Test
225        public void testAccessPropertyEmpty()
226        {
227            assertFalse("Found a key", config.containsKey(TEST_KEY));
228            assertNull("Key has a value", config.getString("test.comment"));
229            assertTrue("Config is not empty", config.isEmpty());
230        }
231    
232        /**
233         * Tests accessing properties if multiple configurations have been added.
234         */
235        @Test
236        public void testAccessPropertyMulti()
237        {
238            config.addConfiguration(setUpTestConfiguration());
239            config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
240            config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
241            assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
242            assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
243            assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
244            assertFalse("Configuration is empty", config.isEmpty());
245            listener.checkEvent(3, 0);
246        }
247    
248        /**
249         * Tests removing a configuration.
250         */
251        @Test
252        public void testRemoveConfiguration()
253        {
254            AbstractConfiguration c = setUpTestConfiguration();
255            config.addConfiguration(c);
256            checkAddConfig(c);
257            assertTrue("Config could not be removed", config.removeConfiguration(c));
258            checkRemoveConfig(c);
259        }
260    
261        /**
262         * Tests removing a configuration by index.
263         */
264        @Test
265        public void testRemoveConfigurationAt()
266        {
267            AbstractConfiguration c = setUpTestConfiguration();
268            config.addConfiguration(c);
269            assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
270            checkRemoveConfig(c);
271        }
272    
273        /**
274         * Tests removing a configuration by name.
275         */
276        @Test
277        public void testRemoveConfigurationByName()
278        {
279            AbstractConfiguration c = setUpTestConfiguration();
280            config.addConfiguration(c, TEST_NAME);
281            assertSame("Wrong config removed", c, config
282                    .removeConfiguration(TEST_NAME));
283            checkRemoveConfig(c);
284        }
285    
286        /**
287         * Tests removing a configuration with a name.
288         */
289        @Test
290        public void testRemoveNamedConfiguration()
291        {
292            AbstractConfiguration c = setUpTestConfiguration();
293            config.addConfiguration(c, TEST_NAME);
294            config.removeConfiguration(c);
295            checkRemoveConfig(c);
296        }
297    
298        /**
299         * Tests removing a named configuration by index.
300         */
301        @Test
302        public void testRemoveNamedConfigurationAt()
303        {
304            AbstractConfiguration c = setUpTestConfiguration();
305            config.addConfiguration(c, TEST_NAME);
306            assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
307            checkRemoveConfig(c);
308        }
309    
310        /**
311         * Tests removing a configuration that was not added prior.
312         */
313        @Test
314        public void testRemoveNonContainedConfiguration()
315        {
316            assertFalse("Could remove non contained config", config
317                    .removeConfiguration(setUpTestConfiguration()));
318            listener.checkEvent(0, 0);
319        }
320    
321        /**
322         * Tests removing a configuration by name, which is not contained.
323         */
324        @Test
325        public void testRemoveConfigurationByUnknownName()
326        {
327            assertNull("Could remove configuration by unknown name", config
328                    .removeConfiguration("unknownName"));
329            listener.checkEvent(0, 0);
330        }
331    
332        /**
333         * Tests whether a configuration was completely removed.
334         *
335         * @param c the removed configuration
336         */
337        private void checkRemoveConfig(AbstractConfiguration c)
338        {
339            assertTrue("Listener was not removed", c.getConfigurationListeners()
340                    .isEmpty());
341            assertEquals("Wrong number of contained configs", 0, config
342                    .getNumberOfConfigurations());
343            assertTrue("Name was not removed", config.getConfigurationNames()
344                    .isEmpty());
345            listener.checkEvent(2, 0);
346        }
347    
348        /**
349         * Tests if an update of a contained configuration leeds to an invalidation
350         * of the combined configuration.
351         */
352        @Test
353        public void testUpdateContainedConfiguration()
354        {
355            AbstractConfiguration c = setUpTestConfiguration();
356            config.addConfiguration(c);
357            c.addProperty("test.otherTest", "yes");
358            assertEquals("New property not found", "yes", config
359                    .getString("test.otherTest"));
360            listener.checkEvent(2, 0);
361        }
362    
363        /**
364         * Tests if setting a node combiner causes an invalidation.
365         */
366        @Test
367        public void testSetNodeCombiner()
368        {
369            NodeCombiner combiner = new UnionCombiner();
370            config.setNodeCombiner(combiner);
371            assertSame("Node combiner was not set", combiner, config
372                    .getNodeCombiner());
373            listener.checkEvent(1, 0);
374        }
375    
376        /**
377         * Tests setting a null node combiner. This should cause an exception.
378         */
379        @Test(expected = IllegalArgumentException.class)
380        public void testSetNullNodeCombiner()
381        {
382            config.setNodeCombiner(null);
383        }
384    
385        /**
386         * Tests cloning a combined configuration.
387         */
388        @Test
389        public void testClone()
390        {
391            config.addConfiguration(setUpTestConfiguration());
392            config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
393            config.addConfiguration(new PropertiesConfiguration(), "props");
394    
395            CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
396            assertEquals("Wrong number of contained configurations", config
397                    .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
398            assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
399                    .getNodeCombiner());
400            assertEquals("Wrong number of names", config.getConfigurationNames()
401                    .size(), cc2.getConfigurationNames().size());
402            assertTrue("Event listeners were cloned", cc2
403                    .getConfigurationListeners().isEmpty());
404    
405            StrictConfigurationComparator comp = new StrictConfigurationComparator();
406            for (int i = 0; i < config.getNumberOfConfigurations(); i++)
407            {
408                assertNotSame("Configuration at " + i + " was not cloned", config
409                        .getConfiguration(i), cc2.getConfiguration(i));
410                assertEquals("Wrong config class at " + i, config.getConfiguration(
411                        i).getClass(), cc2.getConfiguration(i).getClass());
412                assertTrue("Configs not equal at " + i, comp.compare(config
413                        .getConfiguration(i), cc2.getConfiguration(i)));
414            }
415    
416            assertTrue("Combined configs not equal", comp.compare(config, cc2));
417        }
418    
419        /**
420         * Tests if the cloned configuration is decoupled from the original.
421         */
422        @Test
423        public void testCloneModify()
424        {
425            config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
426            CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
427            assertTrue("Name is missing", cc2.getConfigurationNames().contains(
428                    TEST_NAME));
429            cc2.removeConfiguration(TEST_NAME);
430            assertFalse("Names in original changed", config.getConfigurationNames()
431                    .isEmpty());
432        }
433    
434        /**
435         * Tests clearing a combined configuration. This should remove all contained
436         * configurations.
437         */
438        @Test
439        public void testClear()
440        {
441            config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
442            config.addConfiguration(setUpTestConfiguration());
443    
444            config.clear();
445            assertEquals("Still configs contained", 0, config
446                    .getNumberOfConfigurations());
447            assertTrue("Still names contained", config.getConfigurationNames()
448                    .isEmpty());
449            assertTrue("Config is not empty", config.isEmpty());
450    
451            listener.checkEvent(3, 2);
452        }
453    
454        /**
455         * Tests if file-based configurations can be reloaded.
456         */
457        @Test
458        public void testReloading() throws Exception
459        {
460            config.setForceReloadCheck(true);
461            File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
462            File testPropsFile = writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
463            XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
464            c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
465            PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
466            c2.setThrowExceptionOnMissing(true);
467            c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
468            config.addConfiguration(c1);
469            config.addConfiguration(c2);
470            assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
471            assertEquals("Wrong props reload value", 0, config
472                    .getInt("propsReload"));
473    
474            writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
475            assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
476            config.setForceReloadCheck(false);
477            writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 1);
478            assertEquals("Props reload detected though check flag is false", 0, config
479                    .getInt("propsReload"));
480        }
481    
482        /**
483         * Tests whether the reload check works with a subnode configuration. This
484         * test is related to CONFIGURATION-341.
485         */
486        @Test
487        public void testReloadingSubnodeConfig() throws IOException,
488                ConfigurationException
489        {
490            config.setForceReloadCheck(true);
491            File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT,
492                    0);
493            XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
494            c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
495            final String prefix = "reloadCheck";
496            config.addConfiguration(c1, CHILD1, prefix);
497            SubnodeConfiguration sub = config.configurationAt(prefix, true);
498            writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
499            assertEquals("Reload not detected", 1, sub.getInt("xmlReload"));
500        }
501    
502        /**
503         * Tests whether reloading works for a combined configuration nested in
504         * another combined configuration.
505         */
506        @Test
507        public void testReloadingNestedCC() throws IOException,
508                ConfigurationException
509        {
510            config.setForceReloadCheck(true);
511            File testXmlFile =
512                    writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
513            File testPropsFile =
514                    writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
515            XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
516            c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
517            PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
518            c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
519            config.addConfiguration(c2);
520            CombinedConfiguration cc2 = new CombinedConfiguration();
521            cc2.setForceReloadCheck(true);
522            cc2.addConfiguration(c1);
523            config.addConfiguration(cc2);
524            assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
525            writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
526            assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
527        }
528    
529        /**
530         * Prepares a test of the getSource() method.
531         */
532        private void setUpSourceTest()
533        {
534            HierarchicalConfiguration c1 = new HierarchicalConfiguration();
535            PropertiesConfiguration c2 = new PropertiesConfiguration();
536            c1.addProperty(TEST_KEY, TEST_NAME);
537            c2.addProperty("another.key", "test");
538            config.addConfiguration(c1, CHILD1);
539            config.addConfiguration(c2, CHILD2);
540        }
541    
542        /**
543         * Tests the gestSource() method when the source property is defined in a
544         * hierarchical configuration.
545         */
546        @Test
547        public void testGetSourceHierarchical()
548        {
549            setUpSourceTest();
550            assertEquals("Wrong source configuration", config
551                    .getConfiguration(CHILD1), config.getSource(TEST_KEY));
552        }
553    
554        /**
555         * Tests whether the source configuration can be detected for non
556         * hierarchical configurations.
557         */
558        @Test
559        public void testGetSourceNonHierarchical()
560        {
561            setUpSourceTest();
562            assertEquals("Wrong source configuration", config
563                    .getConfiguration(CHILD2), config.getSource("another.key"));
564        }
565    
566        /**
567         * Tests the getSource() method when the passed in key is not contained.
568         * Result should be null in this case.
569         */
570        @Test
571        public void testGetSourceUnknown()
572        {
573            setUpSourceTest();
574            assertNull("Wrong result for unknown key", config
575                    .getSource("an.unknown.key"));
576        }
577    
578        /**
579         * Tests the getSource() method when a null key is passed in. This should
580         * cause an exception.
581         */
582        @Test(expected = IllegalArgumentException.class)
583        public void testGetSourceNull()
584        {
585            config.getSource(null);
586        }
587    
588        /**
589         * Tests the getSource() method when the passed in key belongs to the
590         * combined configuration itself.
591         */
592        @Test
593        public void testGetSourceCombined()
594        {
595            setUpSourceTest();
596            final String key = "yet.another.key";
597            config.addProperty(key, Boolean.TRUE);
598            assertEquals("Wrong source for key", config, config.getSource(key));
599        }
600    
601        /**
602         * Tests the getSource() method when the passed in key refers to multiple
603         * values, which are all defined in the same source configuration.
604         */
605        @Test
606        public void testGetSourceMulti()
607        {
608            setUpSourceTest();
609            final String key = "list.key";
610            config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
611            assertEquals("Wrong source for multi-value property", config
612                    .getConfiguration(CHILD1), config.getSource(key));
613        }
614    
615        /**
616         * Tests the getSource() method when the passed in key refers to multiple
617         * values defined by different sources. This should cause an exception.
618         */
619        @Test(expected = IllegalArgumentException.class)
620        public void testGetSourceMultiSources()
621        {
622            setUpSourceTest();
623            final String key = "list.key";
624            config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
625            config.getConfiguration(CHILD2).addProperty(key, "a,b,c");
626            config.getSource(key);
627        }
628    
629        /**
630         * Tests whether escaped list delimiters are treated correctly.
631         */
632        @Test
633        public void testEscapeListDelimiters()
634        {
635            PropertiesConfiguration sub = new PropertiesConfiguration();
636            sub.addProperty("test.pi", "3\\,1415");
637            config.addConfiguration(sub);
638            assertEquals("Wrong value", "3,1415", config.getString("test.pi"));
639        }
640    
641        /**
642         * Tests whether an invalidate event is fired only after a change. This test
643         * is related to CONFIGURATION-315.
644         */
645        @Test
646        public void testInvalidateAfterChange()
647        {
648            ConfigurationEvent event = new ConfigurationEvent(config, 0, null,
649                    null, true);
650            config.configurationChanged(event);
651            assertEquals("Invalidate event fired", 0, listener.invalidateEvents);
652            event = new ConfigurationEvent(config, 0, null, null, false);
653            config.configurationChanged(event);
654            assertEquals("No invalidate event fired", 1, listener.invalidateEvents);
655        }
656    
657        /**
658         * Tests using a conversion expression engine for child configurations with
659         * strange keys. This test is related to CONFIGURATION-336.
660         */
661        @Test
662        public void testConversionExpressionEngine()
663        {
664            PropertiesConfiguration child = new PropertiesConfiguration();
665            child.addProperty("test(a)", "1,2,3");
666            config.addConfiguration(child);
667            DefaultExpressionEngine engineQuery = new DefaultExpressionEngine();
668            engineQuery.setIndexStart("<");
669            engineQuery.setIndexEnd(">");
670            config.setExpressionEngine(engineQuery);
671            DefaultExpressionEngine engineConvert = new DefaultExpressionEngine();
672            engineConvert.setIndexStart("[");
673            engineConvert.setIndexEnd("]");
674            config.setConversionExpressionEngine(engineConvert);
675            assertEquals("Wrong property 1", "1", config.getString("test(a)<0>"));
676            assertEquals("Wrong property 2", "2", config.getString("test(a)<1>"));
677            assertEquals("Wrong property 3", "3", config.getString("test(a)<2>"));
678        }
679    
680        /**
681         * Tests whether reload operations can cause a deadlock when the combined
682         * configuration is accessed concurrently. This test is related to
683         * CONFIGURATION-344.
684         */
685        @Test
686        public void testDeadlockWithReload() throws ConfigurationException,
687                InterruptedException
688        {
689            final PropertiesConfiguration child = new PropertiesConfiguration(
690                    "test.properties");
691            child.setReloadingStrategy(new FileAlwaysReloadingStrategy());
692            config.addConfiguration(child);
693            final int count = 1000;
694    
695            class TestDeadlockReloadThread extends Thread
696            {
697                boolean error = false;
698    
699                @Override
700                public void run()
701                {
702                    for (int i = 0; i < count && !error; i++)
703                    {
704                        try
705                        {
706                            if (!child.getBoolean("configuration.loaded"))
707                            {
708                                error = true;
709                            }
710                        }
711                        catch (NoSuchElementException nsex)
712                        {
713                            error = true;
714                        }
715                    }
716                }
717            }
718    
719            TestDeadlockReloadThread reloadThread = new TestDeadlockReloadThread();
720            reloadThread.start();
721            for (int i = 0; i < count; i++)
722            {
723                assertEquals("Wrong value of combined property", 10, config
724                        .getInt("test.integer"));
725            }
726            reloadThread.join();
727            assertFalse("Failure in thread", reloadThread.error);
728        }
729    
730        @Test
731        public void testGetConfigurations() throws Exception
732        {
733            config.addConfiguration(setUpTestConfiguration());
734            config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
735            AbstractConfiguration pc = new PropertiesConfiguration();
736            config.addConfiguration(pc, "props");
737            List<AbstractConfiguration> list = config.getConfigurations();
738            assertNotNull("No list of configurations returned", list);
739            assertTrue("Incorrect number of configurations", list.size() == 3);
740            AbstractConfiguration c = list.get(2);
741            assertTrue("Incorrect configuration", c == pc);
742        }
743    
744        @Test
745        public void testGetConfigurationNameList() throws Exception
746        {
747            config.addConfiguration(setUpTestConfiguration());
748            config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
749            AbstractConfiguration pc = new PropertiesConfiguration();
750            config.addConfiguration(pc, "props");
751            List<String> list = config.getConfigurationNameList();
752            assertNotNull("No list of configurations returned", list);
753            assertTrue("Incorrect number of configurations", list.size() == 3);
754            String name = list.get(1);
755            assertNotNull("No name returned", name);
756            assertTrue("Incorrect configuration name", TEST_NAME.equals(name));
757        }
758    
759        /**
760         * Tests whether changes on a sub node configuration that is part of a
761         * combined configuration are detected. This test is related to
762         * CONFIGURATION-368.
763         */
764        @Test
765        public void testReloadWithSubNodeConfig() throws Exception
766        {
767            final String reloadContent = "<config><default><xmlReload1>{0}</xmlReload1></default></config>";
768            config.setForceReloadCheck(true);
769            config.setNodeCombiner(new OverrideCombiner());
770            File testXmlFile1 = writeReloadFile(RELOAD_XML_NAME, reloadContent, 0);
771            final String prefix1 = "default";
772            XMLConfiguration c1 = new XMLConfiguration(testXmlFile1);
773            SubnodeConfiguration sub1 = c1.configurationAt(prefix1, true);
774            assertEquals("Inital test for sub config 1 failed", 0, sub1
775                    .getInt("xmlReload1"));
776            config.addConfiguration(sub1);
777            assertEquals(
778                    "Could not get value for sub config 1 from combined config", 0,
779                    config.getInt("xmlReload1"));
780            c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
781            writeReloadFile(RELOAD_XML_NAME, reloadContent, 1);
782            assertEquals("Reload of sub config 1 not detected", 1, config
783                    .getInt("xmlReload1"));
784        }
785    
786        @Test
787        public void testConcurrentGetAndReload() throws Exception
788        {
789            final int threadCount = 5;
790            final int loopCount = 1000;
791            config.setForceReloadCheck(true);
792            config.setNodeCombiner(new MergeCombiner());
793            final XMLConfiguration xml = new XMLConfiguration("configA.xml");
794            xml.setReloadingStrategy(new FileRandomReloadingStrategy());
795            config.addConfiguration(xml);
796            final XMLConfiguration xml2 = new XMLConfiguration("configB.xml");
797            xml2.setReloadingStrategy(new FileRandomReloadingStrategy());
798            config.addConfiguration(xml2);
799            config.setExpressionEngine(new XPathExpressionEngine());
800    
801            assertEquals(config.getString("/property[@name='config']/@value"), "100");
802    
803            Thread testThreads[] = new Thread[threadCount];
804            int failures[] = new int[threadCount];
805    
806            for (int i = 0; i < testThreads.length; ++i)
807            {
808                testThreads[i] = new ReloadThread(config, failures, i, loopCount);
809                testThreads[i].start();
810            }
811    
812            int totalFailures = 0;
813            for (int i = 0; i < testThreads.length; ++i)
814            {
815                testThreads[i].join();
816                totalFailures += failures[i];
817            }
818            assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
819        }
820    
821        /**
822         * Tests whether a combined configuration can be copied to an XML
823         * configuration. This test is related to CONFIGURATION-445.
824         */
825        @Test
826        public void testCombinedCopyToXML() throws ConfigurationException
827        {
828            XMLConfiguration x1 = new XMLConfiguration();
829            x1.addProperty("key1", "value1");
830            x1.addProperty("key1[@override]", "USER1");
831            x1.addProperty("key2", "value2");
832            x1.addProperty("key2[@override]", "USER2");
833            XMLConfiguration x2 = new XMLConfiguration();
834            x2.addProperty("key2", "value2.2");
835            x2.addProperty("key2[@override]", "USER2");
836            config.setNodeCombiner(new OverrideCombiner());
837            config.addConfiguration(x2);
838            config.addConfiguration(x1);
839            XMLConfiguration x3 = new XMLConfiguration(config);
840            assertEquals("Wrong element value", "value2.2", x3.getString("key2"));
841            assertEquals("Wrong attribute value", "USER2",
842                    x3.getString("key2[@override]"));
843            StringWriter w = new StringWriter();
844            x3.save(w);
845            String s = w.toString();
846            x3 = new XMLConfiguration();
847            x3.load(new StringReader(s));
848            assertEquals("Wrong element value after load", "value2.2",
849                    x3.getString("key2"));
850            assertEquals("Wrong attribute value after load", "USER2",
851                    x3.getString("key2[@override]"));
852        }
853    
854        private class ReloadThread extends Thread
855        {
856            CombinedConfiguration combined;
857            int[] failures;
858            int index;
859            int count;
860    
861            ReloadThread(CombinedConfiguration config, int[] failures, int index, int count)
862            {
863                combined = config;
864                this.failures = failures;
865                this.index = index;
866                this.count = count;
867            }
868            @Override
869            public void run()
870            {
871                failures[index] = 0;
872                for (int i = 0; i < count; i++)
873                {
874                    try
875                    {
876                        String value = combined.getString("/property[@name='config']/@value");
877                        if (value == null || !value.equals("100"))
878                        {
879                            ++failures[index];
880                        }
881                    }
882                    catch (Exception ex)
883                    {
884                        ++failures[index];
885                    }
886                }
887            }
888        }
889    
890        /**
891         * Helper method for writing a file. The file is also added to a list and
892         * will be deleted in teadDown() automatically.
893         *
894         * @param file the file to be written
895         * @param content the file's content
896         * @throws IOException if an error occurs
897         */
898        private void writeFile(File file, String content) throws IOException
899        {
900            PrintWriter out = null;
901            try
902            {
903                out = new PrintWriter(new FileWriter(file));
904                out.print(content);
905            }
906            finally
907            {
908                if (out != null)
909                {
910                    out.close();
911                }
912            }
913        }
914    
915        /**
916         * Helper method for writing a test file. The file will be created in the
917         * test directory. It is also scheduled for automatic deletion after the
918         * test.
919         *
920         * @param fileName the name of the test file
921         * @param content the content of the file
922         * @return the <code>File</code> object for the test file
923         * @throws IOException if an error occurs
924         */
925        private File writeFile(String fileName, String content) throws IOException
926        {
927            File file = new File(folder.getRoot(), fileName);
928            writeFile(file, content);
929            return file;
930        }
931    
932        /**
933         * Writes a file for testing reload operations.
934         *
935         * @param name the name of the reload test file
936         * @param content the content of the file
937         * @param value the value of the reload test property
938         * @return the file that was written
939         * @throws IOException if an error occurs
940         */
941        private File writeReloadFile(String name, String content, int value)
942                throws IOException
943        {
944            return writeFile(name, MessageFormat.format(content, new Object[] {
945                new Integer(value)
946            }));
947        }
948    
949        /**
950         * Helper method for creating a test configuration to be added to the
951         * combined configuration.
952         *
953         * @return the test configuration
954         */
955        private AbstractConfiguration setUpTestConfiguration()
956        {
957            HierarchicalConfiguration config = new HierarchicalConfiguration();
958            config.addProperty(TEST_KEY, Boolean.TRUE);
959            config.addProperty("test.comment", "This is a test");
960            return config;
961        }
962    
963        /**
964         * Test event listener class for checking if the expected invalidate events
965         * are fired.
966         */
967        static class CombinedListener implements ConfigurationListener
968        {
969            int invalidateEvents;
970    
971            int otherEvents;
972    
973            public void configurationChanged(ConfigurationEvent event)
974            {
975                if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
976                {
977                    invalidateEvents++;
978                }
979                else
980                {
981                    otherEvents++;
982                }
983            }
984    
985            /**
986             * Checks if the expected number of events was fired.
987             *
988             * @param expectedInvalidate the expected number of invalidate events
989             * @param expectedOthers the expected number of other events
990             */
991            public void checkEvent(int expectedInvalidate, int expectedOthers)
992            {
993                Assert.assertEquals("Wrong number of invalidate events",
994                        expectedInvalidate, invalidateEvents);
995                Assert.assertEquals("Wrong number of other events", expectedOthers,
996                        otherEvents);
997            }
998        }
999    }