| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.configuration2; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertInstanceOf; |
| import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertNotSame; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertSame; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.commons.configuration2.SynchronizerTestImpl.Methods; |
| import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; |
| import org.apache.commons.configuration2.event.ConfigurationEvent; |
| import org.apache.commons.configuration2.event.EventListener; |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; |
| import org.apache.commons.configuration2.io.FileHandler; |
| import org.apache.commons.configuration2.sync.LockMode; |
| import org.apache.commons.configuration2.sync.ReadWriteSynchronizer; |
| import org.apache.commons.configuration2.sync.Synchronizer; |
| import org.apache.commons.configuration2.tree.DefaultExpressionEngine; |
| import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols; |
| import org.apache.commons.configuration2.tree.ImmutableNode; |
| import org.apache.commons.configuration2.tree.NodeCombiner; |
| import org.apache.commons.configuration2.tree.NodeModel; |
| import org.apache.commons.configuration2.tree.OverrideCombiner; |
| import org.apache.commons.configuration2.tree.UnionCombiner; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| /** |
| * Test class for CombinedConfiguration. |
| */ |
| public class TestCombinedConfiguration { |
| |
| /** |
| * Test event listener class for checking if the expected invalidate events are fired. |
| */ |
| private static final class CombinedListener implements EventListener<ConfigurationEvent> { |
| |
| private int invalidateEvents; |
| |
| private int otherEvents; |
| |
| /** |
| * Checks if the expected number of events was fired. |
| * |
| * @param expectedInvalidate the expected number of invalidate events |
| * @param expectedOthers the expected number of other events |
| */ |
| public void checkEvent(final int expectedInvalidate, final int expectedOthers) { |
| assertEquals(expectedInvalidate, invalidateEvents); |
| assertEquals(expectedOthers, otherEvents); |
| } |
| |
| @Override |
| public void onEvent(final ConfigurationEvent event) { |
| if (event.getEventType() == CombinedConfiguration.COMBINED_INVALIDATE) { |
| invalidateEvents++; |
| } else { |
| otherEvents++; |
| } |
| } |
| } |
| |
| /** |
| * A test thread performing reads on a combined configuration. This thread reads a certain property from the |
| * configuration. If everything works well, this property should have at least one and at most two values. |
| */ |
| private static final class ReadThread extends Thread { |
| |
| /** The configuration to be accessed. */ |
| private final Configuration config; |
| |
| /** The latch for synchronizing thread start. */ |
| private final CountDownLatch startLatch; |
| |
| /** A counter for read errors. */ |
| private final AtomicInteger errorCount; |
| |
| /** The number of reads to be performed. */ |
| private final int numberOfReads; |
| |
| /** |
| * Creates a new instance of {@code ReadThread}. |
| * |
| * @param readConfig the configuration to be read |
| * @param latch the latch for synchronizing thread start |
| * @param errCnt the counter for read errors |
| * @param readCount the number of reads to be performed |
| */ |
| public ReadThread(final Configuration readConfig, final CountDownLatch latch, final AtomicInteger errCnt, final int readCount) { |
| config = readConfig; |
| startLatch = latch; |
| errorCount = errCnt; |
| numberOfReads = readCount; |
| } |
| |
| /** |
| * Reads the test property from the associated configuration. Its values are checked. |
| */ |
| private void readConfiguration() { |
| final List<Object> values = config.getList(KEY_CONCURRENT); |
| if (values.size() < 1 || values.size() > 2) { |
| errorCount.incrementAndGet(); |
| } else { |
| boolean ok = true; |
| for (final Object value : values) { |
| if (!TEST_NAME.equals(value)) { |
| ok = false; |
| } |
| } |
| if (!ok) { |
| errorCount.incrementAndGet(); |
| } |
| } |
| } |
| |
| /** |
| * Reads from the test configuration. |
| */ |
| @Override |
| public void run() { |
| try { |
| startLatch.await(); |
| for (int i = 0; i < numberOfReads; i++) { |
| readConfiguration(); |
| } |
| } catch (final Exception e) { |
| errorCount.incrementAndGet(); |
| } |
| } |
| } |
| |
| /** |
| * A test thread performing updates on a test configuration. This thread modifies configurations which are children of a |
| * combined configuration. Each update operation adds a value to one of the child configurations and removes it from |
| * another one (which contained it before). So if concurrent reads are performed, the test property should always have |
| * between 1 and 2 values. |
| */ |
| private static final class WriteThread extends Thread { |
| |
| /** The list with the child configurations. */ |
| private final List<Configuration> testConfigs; |
| |
| /** The latch for synchronizing thread start. */ |
| private final CountDownLatch startLatch; |
| |
| /** A counter for errors. */ |
| private final AtomicInteger errorCount; |
| |
| /** The number of write operations to be performed. */ |
| private final int numberOfWrites; |
| |
| /** The index of the child configuration containing the test property. */ |
| private int currentChildConfigIdx; |
| |
| /** |
| * Creates a new instance of {@code WriteThread}. |
| * |
| * @param cc the test combined configuration |
| * @param latch the latch for synchronizing test start |
| * @param errCnt a counter for errors |
| * @param writeCount the number of writes to be performed |
| */ |
| public WriteThread(final CombinedConfiguration cc, final CountDownLatch latch, final AtomicInteger errCnt, final int writeCount) { |
| testConfigs = cc.getConfigurations(); |
| startLatch = latch; |
| errorCount = errCnt; |
| numberOfWrites = writeCount; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| startLatch.await(); |
| for (int i = 0; i < numberOfWrites; i++) { |
| updateConfigurations(); |
| } |
| } catch (final InterruptedException e) { |
| errorCount.incrementAndGet(); |
| } |
| } |
| |
| /** |
| * Performs the update operation. |
| */ |
| private void updateConfigurations() { |
| final int newIdx = (currentChildConfigIdx + 1) % testConfigs.size(); |
| testConfigs.get(newIdx).addProperty(KEY_CONCURRENT, TEST_NAME); |
| testConfigs.get(currentChildConfigIdx).clearProperty(KEY_CONCURRENT); |
| currentChildConfigIdx = newIdx; |
| } |
| } |
| |
| /** Constant for the name of a sub configuration. */ |
| private static final String TEST_NAME = "SUBCONFIG"; |
| |
| /** Constant for a test key. */ |
| private static final String TEST_KEY = "test.value"; |
| |
| /** Constant for a key to be used for a concurrent test. */ |
| private static final String KEY_CONCURRENT = "concurrent.access.test"; |
| |
| /** Constant for the name of the first child configuration. */ |
| private static final String CHILD1 = TEST_NAME + "1"; |
| |
| /** Constant for the name of the second child configuration. */ |
| private static final String CHILD2 = TEST_NAME + "2"; |
| |
| /** Constant for the key for a sub configuration. */ |
| private static final String SUB_KEY = "test.sub.config"; |
| |
| /** |
| * Helper method for creating a test configuration to be added to the combined configuration. |
| * |
| * @return the test configuration |
| */ |
| private static AbstractConfiguration setUpTestConfiguration() { |
| final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration(); |
| config.addProperty(TEST_KEY, Boolean.TRUE); |
| config.addProperty("test.comment", "This is a test"); |
| return config; |
| } |
| |
| /** The configuration to be tested. */ |
| private CombinedConfiguration config; |
| |
| /** The test event listener. */ |
| private CombinedListener listener; |
| |
| /** |
| * Checks if a configuration was correctly added to the combined config. |
| * |
| * @param c the config to check |
| */ |
| private void checkAddConfig(final AbstractConfiguration c) { |
| final Collection<EventListener<? super ConfigurationEvent>> listeners = c.getEventListeners(ConfigurationEvent.ANY); |
| assertEquals(1, listeners.size()); |
| assertTrue(listeners.contains(config)); |
| } |
| |
| /** |
| * Helper method for testing that the combined root node has not yet been constructed. |
| */ |
| private void checkCombinedRootNotConstructed() { |
| assertTrue(config.getModel().getNodeHandler().getRootNode().getChildren().isEmpty()); |
| } |
| |
| /** |
| * Checks the configurationsAt() method. |
| * |
| * @param withUpdates flag whether updates are supported |
| */ |
| private void checkConfigurationsAt(final boolean withUpdates) { |
| setUpSubConfigTest(); |
| final List<HierarchicalConfiguration<ImmutableNode>> subs = config.configurationsAt(SUB_KEY, withUpdates); |
| assertEquals(1, subs.size()); |
| assertTrue(subs.get(0).getBoolean(TEST_KEY)); |
| } |
| |
| /** |
| * Tests whether a configuration was completely removed. |
| * |
| * @param c the removed configuration |
| */ |
| private void checkRemoveConfig(final AbstractConfiguration c) { |
| assertTrue(c.getEventListeners(ConfigurationEvent.ANY).isEmpty()); |
| assertEquals(0, config.getNumberOfConfigurations()); |
| assertTrue(config.getConfigurationNames().isEmpty()); |
| listener.checkEvent(2, 0); |
| } |
| |
| @BeforeEach |
| public void setUp() throws Exception { |
| config = new CombinedConfiguration(); |
| listener = new CombinedListener(); |
| config.addEventListener(ConfigurationEvent.ANY, listener); |
| } |
| |
| /** |
| * Prepares a test of the getSource() method. |
| */ |
| private void setUpSourceTest() { |
| final BaseHierarchicalConfiguration c1 = new BaseHierarchicalConfiguration(); |
| final PropertiesConfiguration c2 = new PropertiesConfiguration(); |
| c1.addProperty(TEST_KEY, TEST_NAME); |
| c2.addProperty("another.key", "test"); |
| config.addConfiguration(c1, CHILD1); |
| config.addConfiguration(c2, CHILD2); |
| } |
| |
| /** |
| * Prepares the test configuration for a test for sub configurations. Some child configurations are added. |
| * |
| * @return the sub configuration at the test sub key |
| */ |
| private AbstractConfiguration setUpSubConfigTest() { |
| final AbstractConfiguration srcConfig = setUpTestConfiguration(); |
| config.addConfiguration(srcConfig, "source", SUB_KEY); |
| config.addConfiguration(setUpTestConfiguration()); |
| config.addConfiguration(setUpTestConfiguration(), "otherTest", "other.prefix"); |
| return srcConfig; |
| } |
| |
| /** |
| * Prepares a test for synchronization. This method installs a test synchronizer and adds some test configurations. |
| * |
| * @return the test synchronizer |
| */ |
| private SynchronizerTestImpl setUpSynchronizerTest() { |
| setUpSourceTest(); |
| final SynchronizerTestImpl sync = new SynchronizerTestImpl(); |
| config.setSynchronizer(sync); |
| return sync; |
| } |
| |
| /** |
| * Tests accessing properties if no configurations have been added. |
| */ |
| @Test |
| void testAccessPropertyEmpty() { |
| assertFalse(config.containsKey(TEST_KEY)); |
| assertNull(config.getString("test.comment")); |
| assertTrue(config.isEmpty()); |
| } |
| |
| /** |
| * Tests accessing properties if multiple configurations have been added. |
| */ |
| @Test |
| void testAccessPropertyMulti() { |
| config.addConfiguration(setUpTestConfiguration()); |
| config.addConfiguration(setUpTestConfiguration(), null, "prefix1"); |
| config.addConfiguration(setUpTestConfiguration(), null, "prefix2"); |
| assertTrue(config.getBoolean(TEST_KEY)); |
| assertTrue(config.getBoolean("prefix1." + TEST_KEY)); |
| assertTrue(config.getBoolean("prefix2." + TEST_KEY)); |
| assertFalse(config.isEmpty()); |
| listener.checkEvent(3, 0); |
| } |
| |
| /** |
| * Tests adding a configuration (without further information). |
| */ |
| @Test |
| void testAddConfiguration() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c); |
| checkAddConfig(c); |
| assertEquals(1, config.getNumberOfConfigurations()); |
| assertTrue(config.getConfigurationNames().isEmpty()); |
| assertSame(c, config.getConfiguration(0)); |
| assertTrue(config.getBoolean(TEST_KEY)); |
| listener.checkEvent(1, 0); |
| } |
| |
| /** |
| * Tests adding a configuration and specifying an at position. |
| */ |
| @Test |
| void testAddConfigurationAt() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, null, "my"); |
| checkAddConfig(c); |
| assertTrue(config.getBoolean("my." + TEST_KEY)); |
| } |
| |
| /** |
| * Tests adding a configuration with a complex at position. Here the at path contains a dot, which must be escaped. |
| */ |
| @Test |
| void testAddConfigurationComplexAt() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, null, "This..is.a.complex"); |
| checkAddConfig(c); |
| assertTrue(config.getBoolean("This..is.a.complex." + TEST_KEY)); |
| } |
| |
| /** |
| * Tests whether adding a new configuration is synchronized. |
| */ |
| @Test |
| void testAddConfigurationSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| config.addConfiguration(new BaseHierarchicalConfiguration()); |
| sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests adding a configuration with a name. |
| */ |
| @Test |
| void testAddConfigurationWithName() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, TEST_NAME); |
| checkAddConfig(c); |
| assertEquals(1, config.getNumberOfConfigurations()); |
| assertSame(c, config.getConfiguration(0)); |
| assertSame(c, config.getConfiguration(TEST_NAME)); |
| final Set<String> names = config.getConfigurationNames(); |
| assertEquals(1, names.size()); |
| assertTrue(names.contains(TEST_NAME)); |
| assertTrue(config.getBoolean(TEST_KEY)); |
| listener.checkEvent(1, 0); |
| } |
| |
| /** |
| * Tests adding a configuration with a name when this name already exists. This should cause an exception. |
| */ |
| @Test |
| void testAddConfigurationWithNameTwice() { |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME); |
| final Configuration configuration = setUpTestConfiguration(); |
| assertThrows(ConfigurationRuntimeException.class, () -> config.addConfiguration(configuration, TEST_NAME, "prefix")); |
| } |
| |
| /** |
| * Tests adding a null configuration. This should cause an exception to be thrown. |
| */ |
| @Test |
| void testAddNullConfiguration() { |
| assertThrows(IllegalArgumentException.class, () -> config.addConfiguration(null)); |
| } |
| |
| /** |
| * Tests clearing a combined configuration. This should remove all contained configurations. |
| */ |
| @Test |
| void testClear() { |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test"); |
| config.addConfiguration(setUpTestConfiguration()); |
| |
| config.clear(); |
| assertEquals(0, config.getNumberOfConfigurations()); |
| assertTrue(config.getConfigurationNames().isEmpty()); |
| assertTrue(config.isEmpty()); |
| |
| listener.checkEvent(3, 2); |
| } |
| |
| /** |
| * Tests whether the combined configuration removes itself as change listener from the child configurations on a clear |
| * operation. This test is related to CONFIGURATION-572. |
| */ |
| @Test |
| void testClearRemoveChildListener() { |
| final AbstractConfiguration child = setUpTestConfiguration(); |
| config.addConfiguration(child); |
| |
| config.clear(); |
| for (final EventListener<?> listener : child.getEventListeners(ConfigurationEvent.ANY)) { |
| assertNotEquals(config, listener); |
| } |
| } |
| |
| /** |
| * Tests cloning a combined configuration. |
| */ |
| @Test |
| void testClone() { |
| config.addConfiguration(setUpTestConfiguration()); |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2"); |
| config.addConfiguration(new PropertiesConfiguration(), "props"); |
| |
| final CombinedConfiguration cc2 = (CombinedConfiguration) config.clone(); |
| assertNotNull(cc2.getModel().getNodeHandler().getRootNode()); |
| assertEquals(config.getNumberOfConfigurations(), cc2.getNumberOfConfigurations()); |
| assertSame(config.getNodeCombiner(), cc2.getNodeCombiner()); |
| assertEquals(config.getConfigurationNames().size(), cc2.getConfigurationNames().size()); |
| assertTrue(Collections.disjoint(cc2.getEventListeners(ConfigurationEvent.ANY), config.getEventListeners(ConfigurationEvent.ANY))); |
| |
| final StrictConfigurationComparator comp = new StrictConfigurationComparator(); |
| for (int i = 0; i < config.getNumberOfConfigurations(); i++) { |
| assertNotSame(config.getConfiguration(i), cc2.getConfiguration(i), "Configuration at " + i + " was not cloned"); |
| assertEquals(config.getConfiguration(i).getClass(), cc2.getConfiguration(i).getClass(), "Wrong config class at " + i); |
| assertTrue(comp.compare(config.getConfiguration(i), cc2.getConfiguration(i)), "Configs not equal at " + i); |
| } |
| |
| assertTrue(comp.compare(config, cc2)); |
| } |
| |
| /** |
| * Tests if the cloned configuration is decoupled from the original. |
| */ |
| @Test |
| void testCloneModify() { |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME); |
| final CombinedConfiguration cc2 = (CombinedConfiguration) config.clone(); |
| assertTrue(cc2.getConfigurationNames().contains(TEST_NAME)); |
| cc2.removeConfiguration(TEST_NAME); |
| assertFalse(config.getConfigurationNames().isEmpty()); |
| } |
| |
| /** |
| * Tests whether cloning of a configuration is correctly synchronized. |
| */ |
| @Test |
| void testCloneSynchronized() { |
| setUpSourceTest(); |
| config.lock(LockMode.READ); // Causes the root node to be constructed |
| config.unlock(LockMode.READ); |
| final SynchronizerTestImpl sync = new SynchronizerTestImpl(); |
| config.setSynchronizer(sync); |
| config.clone(); |
| // clone() of base class is wrapped by another read lock |
| sync.verifyStart(Methods.BEGIN_READ, Methods.BEGIN_READ); |
| sync.verifyEnd(Methods.END_READ, Methods.END_READ); |
| } |
| |
| /** |
| * Tests whether a combined configuration can be copied to an XML configuration. This test is related to |
| * CONFIGURATION-445. |
| */ |
| @Test |
| void testCombinedCopyToXML() throws ConfigurationException { |
| final XMLConfiguration x1 = new XMLConfiguration(); |
| x1.addProperty("key1", "value1"); |
| x1.addProperty("key1[@override]", "USER1"); |
| x1.addProperty("key2", "value2"); |
| x1.addProperty("key2[@override]", "USER2"); |
| final XMLConfiguration x2 = new XMLConfiguration(); |
| x2.addProperty("key2", "value2.2"); |
| x2.addProperty("key2[@override]", "USER2"); |
| config.setNodeCombiner(new OverrideCombiner()); |
| config.addConfiguration(x2); |
| config.addConfiguration(x1); |
| XMLConfiguration x3 = new XMLConfiguration(config); |
| assertEquals("value2.2", x3.getString("key2")); |
| assertEquals("USER2", x3.getString("key2[@override]")); |
| final StringWriter w = new StringWriter(); |
| new FileHandler(x3).save(w); |
| final String s = w.toString(); |
| x3 = new XMLConfiguration(); |
| new FileHandler(x3).load(new StringReader(s)); |
| assertEquals("value2.2", x3.getString("key2")); |
| assertEquals("USER2", x3.getString("key2[@override]")); |
| } |
| |
| /** |
| * Tests concurrent read and write access on a combined configuration. There are multiple reader threads and a single |
| * writer thread. It is checked that no inconsistencies occur. |
| */ |
| @Test |
| void testConcurrentAccess() throws ConfigurationException, InterruptedException { |
| // populate the test combined configuration |
| setUpSourceTest(); |
| final XMLConfiguration xmlConf = new XMLConfiguration(); |
| new FileHandler(xmlConf).load(ConfigurationAssert.getTestFile("test.xml")); |
| config.addConfiguration(xmlConf); |
| final PropertiesConfiguration propConf = new PropertiesConfiguration(); |
| new FileHandler(propConf).load(ConfigurationAssert.getTestFile("test.properties")); |
| for (int i = 0; i < 8; i++) { |
| config.addConfiguration(new BaseHierarchicalConfiguration()); |
| } |
| config.getConfiguration(0).addProperty(KEY_CONCURRENT, TEST_NAME); |
| |
| // Set a single synchronizer for all involved configurations |
| final Synchronizer sync = new ReadWriteSynchronizer(); |
| config.setSynchronizer(sync); |
| for (final Configuration c : config.getConfigurations()) { |
| c.setSynchronizer(sync); |
| } |
| |
| // setup test threads |
| final int numberOfReaders = 3; |
| final int readCount = 5000; |
| final int writeCount = 3000; |
| final CountDownLatch latch = new CountDownLatch(1); |
| final AtomicInteger errorCount = new AtomicInteger(); |
| final Collection<Thread> threads = new ArrayList<>(numberOfReaders + 1); |
| final Thread writeThread = new WriteThread(config, latch, errorCount, writeCount); |
| writeThread.start(); |
| threads.add(writeThread); |
| for (int i = 0; i < numberOfReaders; i++) { |
| final Thread readThread = new ReadThread(config, latch, errorCount, readCount); |
| readThread.start(); |
| threads.add(readThread); |
| } |
| |
| // perform test |
| latch.countDown(); |
| for (final Thread t : threads) { |
| t.join(); |
| } |
| assertEquals(0, errorCount.get()); |
| } |
| |
| /** |
| * Tests whether sub configurations can be created from a key. |
| */ |
| @Test |
| void testConfigurationsAt() { |
| checkConfigurationsAt(false); |
| } |
| |
| /** |
| * Tests whether sub configurations can be created which are attached. |
| */ |
| @Test |
| void testConfigurationsAtWithUpdates() { |
| checkConfigurationsAt(true); |
| } |
| |
| /** |
| * Tests using a conversion expression engine for child configurations with strange keys. This test is related to |
| * CONFIGURATION-336. |
| */ |
| @Test |
| void testConversionExpressionEngine() { |
| final PropertiesConfiguration child = new PropertiesConfiguration(); |
| child.setListDelimiterHandler(new DefaultListDelimiterHandler(',')); |
| child.addProperty("test(a)", "1,2,3"); |
| config.addConfiguration(child); |
| final DefaultExpressionEngine engineQuery = new DefaultExpressionEngine( |
| new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS).setIndexStart("<").setIndexEnd(">").create()); |
| config.setExpressionEngine(engineQuery); |
| final DefaultExpressionEngine engineConvert = new DefaultExpressionEngine( |
| new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS).setIndexStart("[").setIndexEnd("]").create()); |
| config.setConversionExpressionEngine(engineConvert); |
| assertEquals("1", config.getString("test(a)<0>")); |
| assertEquals("2", config.getString("test(a)<1>")); |
| assertEquals("3", config.getString("test(a)<2>")); |
| } |
| |
| /** |
| * Tests whether escaped list delimiters are treated correctly. |
| */ |
| @Test |
| void testEscapeListDelimiters() { |
| final PropertiesConfiguration sub = new PropertiesConfiguration(); |
| sub.setListDelimiterHandler(new DefaultListDelimiterHandler(',')); |
| sub.addProperty("test.pi", "3\\,1415"); |
| config.addConfiguration(sub); |
| assertEquals("3,1415", config.getString("test.pi")); |
| } |
| |
| /** |
| * Tests whether access to a configuration by index is correctly synchronized. |
| */ |
| @Test |
| void testGetConfigurationByIdxSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertNotNull(config.getConfiguration(0)); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests whether access to a configuration by name is correctly synchronized. |
| */ |
| @Test |
| void testGetConfigurationByNameSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertNotNull(config.getConfiguration(CHILD1)); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| @Test |
| void testGetConfigurationNameList() throws Exception { |
| config.addConfiguration(setUpTestConfiguration()); |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2"); |
| final AbstractConfiguration pc = new PropertiesConfiguration(); |
| config.addConfiguration(pc, "props"); |
| final List<String> list = config.getConfigurationNameList(); |
| assertNotNull(list); |
| assertEquals(3, list.size()); |
| final String name = list.get(1); |
| assertNotNull(name); |
| assertEquals(TEST_NAME, name); |
| } |
| |
| /** |
| * Tests whether querying the name list of child configurations is synchronized. |
| */ |
| @Test |
| void testGetConfigurationNameListSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertFalse(config.getConfigurationNameList().isEmpty()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests whether querying the name set of child configurations is synchronized. |
| */ |
| @Test |
| void testGetConfigurationNamesSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertFalse(config.getConfigurationNames().isEmpty()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| @Test |
| void testGetConfigurations() throws Exception { |
| config.addConfiguration(setUpTestConfiguration()); |
| config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2"); |
| final AbstractConfiguration pc = new PropertiesConfiguration(); |
| config.addConfiguration(pc, "props"); |
| final List<Configuration> list = config.getConfigurations(); |
| assertNotNull(list); |
| assertEquals(3, list.size()); |
| final Configuration c = list.get(2); |
| assertSame(pc, c); |
| } |
| |
| /** |
| * Tests whether querying the list of child configurations is synchronized. |
| */ |
| @Test |
| void testGetConfigurationsSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertFalse(config.getConfigurations().isEmpty()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests whether read access to the conversion expression engine is synchronized. |
| */ |
| @Test |
| void testGetConversionExpressionEngineSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertNull(config.getConversionExpressionEngine()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests CONFIGURATION-799. |
| */ |
| @Test |
| void testGetKeys() { |
| // Set up |
| final BaseConfiguration conf1 = new BaseConfiguration(); |
| final String key = "x1"; |
| conf1.addProperty(key, 1); |
| assertEquals(1, conf1.getProperty(key)); |
| |
| final CombinedConfiguration conf2 = new CombinedConfiguration(); |
| conf2.addConfiguration(conf1, null, ""); |
| assertEquals(conf1, conf2.getConfiguration(0)); |
| |
| // Actual test |
| final Iterator<String> keys = conf2.getKeys(); |
| assertEquals(key, keys.next()); |
| assertFalse(keys.hasNext()); |
| } |
| |
| /** |
| * Tests whether getNodeCombiner() is correctly synchronized. |
| */ |
| @Test |
| void testGetNodeCombinerSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertNotNull(config.getNodeCombiner()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests whether querying the number of child configurations is synchronized. |
| */ |
| @Test |
| void testGetNumberOfConfigurationsSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertEquals(2, config.getNumberOfConfigurations()); |
| sync.verify(Methods.BEGIN_READ, Methods.END_READ); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests the getSource() method when the passed in key belongs to the combined configuration itself. |
| */ |
| @Test |
| void testGetSourceCombined() { |
| setUpSourceTest(); |
| final String key = "yet.another.key"; |
| config.addProperty(key, Boolean.TRUE); |
| assertEquals(config, config.getSource(key)); |
| } |
| |
| /** |
| * Tests the gestSource() method when the source property is defined in a hierarchical configuration. |
| */ |
| @Test |
| void testGetSourceHierarchical() { |
| setUpSourceTest(); |
| assertEquals(config.getConfiguration(CHILD1), config.getSource(TEST_KEY)); |
| } |
| |
| /** |
| * Tests the getSource() method when the passed in key refers to multiple values, which are all defined in the same |
| * source configuration. |
| */ |
| @Test |
| void testGetSourceMulti() { |
| setUpSourceTest(); |
| final String key = "list.key"; |
| config.getConfiguration(CHILD1).addProperty(key, "1,2,3"); |
| assertEquals(config.getConfiguration(CHILD1), config.getSource(key)); |
| } |
| |
| /** |
| * Tests the getSource() method when the passed in key refers to multiple values defined by different sources. This |
| * should cause an exception. |
| */ |
| @Test |
| void testGetSourceMultiSources() { |
| setUpSourceTest(); |
| final String key = "list.key"; |
| config.getConfiguration(CHILD1).addProperty(key, "1,2,3"); |
| config.getConfiguration(CHILD2).addProperty(key, "a,b,c"); |
| assertThrows(IllegalArgumentException.class, () -> config.getSource(key)); |
| } |
| |
| /** |
| * Tests whether the source configuration can be detected for non hierarchical configurations. |
| */ |
| @Test |
| void testGetSourceNonHierarchical() { |
| setUpSourceTest(); |
| assertEquals(config.getConfiguration(CHILD2), config.getSource("another.key")); |
| } |
| |
| /** |
| * Tests the getSource() method when a null key is passed in. This should cause an exception. |
| */ |
| @Test |
| void testGetSourceNull() { |
| assertThrows(IllegalArgumentException.class, () -> config.getSource(null)); |
| } |
| |
| /** |
| * Tests whether multiple sources of a key can be retrieved. |
| */ |
| @Test |
| void testGetSourcesMultiSources() { |
| setUpSourceTest(); |
| final String key = "list.key"; |
| config.getConfiguration(CHILD1).addProperty(key, "1,2,3"); |
| config.getConfiguration(CHILD2).addProperty(key, "a,b,c"); |
| final Set<Configuration> sources = config.getSources(key); |
| assertEquals(2, sources.size()); |
| assertTrue(sources.contains(config.getConfiguration(CHILD1))); |
| assertTrue(sources.contains(config.getConfiguration(CHILD2))); |
| } |
| |
| /** |
| * Tests getSources() for a non existing key. |
| */ |
| @Test |
| void testGetSourcesUnknownKey() { |
| setUpSourceTest(); |
| assertTrue(config.getSources("non.existing,key").isEmpty()); |
| } |
| |
| /** |
| * Tests whether getSource() is correctly synchronized. |
| */ |
| @Test |
| void testGetSourceSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| assertNotNull(config.getSource(TEST_KEY)); |
| sync.verifyStart(Methods.BEGIN_READ); |
| sync.verifyEnd(Methods.END_READ); |
| } |
| |
| /** |
| * Tests the getSource() method when the passed in key is not contained. Result should be null in this case. |
| */ |
| @Test |
| void testGetSourceUnknown() { |
| setUpSourceTest(); |
| assertNull(config.getSource("an.unknown.key")); |
| } |
| |
| /** |
| * Tests getSource() if a child configuration is again a combined configuration. |
| */ |
| @Test |
| void testGetSourceWithCombinedChildConfiguration() { |
| setUpSourceTest(); |
| final CombinedConfiguration cc = new CombinedConfiguration(); |
| cc.addConfiguration(config); |
| assertEquals(config, cc.getSource(TEST_KEY)); |
| } |
| |
| /** |
| * Tests accessing a newly created combined configuration. |
| */ |
| @Test |
| void testInit() { |
| assertEquals(0, config.getNumberOfConfigurations()); |
| assertTrue(config.getConfigurationNames().isEmpty()); |
| assertInstanceOf(UnionCombiner.class, config.getNodeCombiner()); |
| assertNull(config.getConfiguration(TEST_NAME)); |
| } |
| |
| /** |
| * Tests whether only a single invalidate event is fired for a change. This test is related to CONFIGURATION-315. |
| */ |
| @Test |
| void testInvalidateEventBeforeAndAfterChange() { |
| ConfigurationEvent event = new ConfigurationEvent(config, ConfigurationEvent.ANY, null, null, true); |
| config.onEvent(event); |
| assertEquals(1, listener.invalidateEvents); |
| event = new ConfigurationEvent(config, ConfigurationEvent.ANY, null, null, false); |
| config.onEvent(event); |
| assertEquals(1, listener.invalidateEvents); |
| } |
| |
| /** |
| * Tests whether invalidate() performs correct synchronization. |
| */ |
| @Test |
| void testInvalidateSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| config.invalidate(); |
| sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE); |
| } |
| |
| /** |
| * Tests whether requested locks are freed correctly if an exception occurs while constructing the root node. |
| */ |
| @Test |
| void testLockHandlingWithExceptionWhenConstructingRootNode() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| final RuntimeException testEx = new ConfigurationRuntimeException("Test exception"); |
| final BaseHierarchicalConfiguration childEx = new BaseHierarchicalConfiguration() { |
| @Override |
| public NodeModel<ImmutableNode> getModel() { |
| throw testEx; |
| } |
| }; |
| config.addConfiguration(childEx); |
| final Exception ex = assertThrows(Exception.class, () -> config.lock(LockMode.READ)); |
| assertEquals(testEx, ex); |
| // 1 x add configuration, then obtain read lock and create root node |
| sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ, Methods.END_READ, Methods.BEGIN_WRITE, Methods.END_WRITE); |
| } |
| |
| /** |
| * Tests removing a configuration. |
| */ |
| @Test |
| void testRemoveConfiguration() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c); |
| checkAddConfig(c); |
| assertTrue(config.removeConfiguration(c)); |
| checkRemoveConfig(c); |
| } |
| |
| /** |
| * Tests removing a configuration by index. |
| */ |
| @Test |
| void testRemoveConfigurationAt() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c); |
| assertSame(c, config.removeConfigurationAt(0)); |
| checkRemoveConfig(c); |
| } |
| |
| /** |
| * Tests removing a configuration by name. |
| */ |
| @Test |
| void testRemoveConfigurationByName() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, TEST_NAME); |
| assertSame(c, config.removeConfiguration(TEST_NAME)); |
| checkRemoveConfig(c); |
| } |
| |
| /** |
| * Tests removing a configuration by name, which is not contained. |
| */ |
| @Test |
| void testRemoveConfigurationByUnknownName() { |
| assertNull(config.removeConfiguration("unknownName")); |
| listener.checkEvent(0, 0); |
| } |
| |
| /** |
| * Tests removing a configuration with a name. |
| */ |
| @Test |
| void testRemoveNamedConfiguration() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, TEST_NAME); |
| config.removeConfiguration(c); |
| checkRemoveConfig(c); |
| } |
| |
| /** |
| * Tests removing a named configuration by index. |
| */ |
| @Test |
| void testRemoveNamedConfigurationAt() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c, TEST_NAME); |
| assertSame(c, config.removeConfigurationAt(0)); |
| checkRemoveConfig(c); |
| } |
| |
| /** |
| * Tests removing a configuration that was not added prior. |
| */ |
| @Test |
| void testRemoveNonContainedConfiguration() { |
| assertFalse(config.removeConfiguration(setUpTestConfiguration())); |
| listener.checkEvent(0, 0); |
| } |
| |
| /** |
| * Tests whether write access to the conversion expression engine is synchronized. |
| */ |
| @Test |
| void testSetConversionExpressionEngineSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| config.setConversionExpressionEngine(new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS)); |
| sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests if setting a node combiner causes an invalidation. |
| */ |
| @Test |
| void testSetNodeCombiner() { |
| final NodeCombiner combiner = new UnionCombiner(); |
| config.setNodeCombiner(combiner); |
| assertSame(combiner, config.getNodeCombiner()); |
| listener.checkEvent(1, 0); |
| } |
| |
| /** |
| * Tests whether setNodeCombiner() is correctly synchronized. |
| */ |
| @Test |
| void testSetNodeCombinerSynchronized() { |
| final SynchronizerTestImpl sync = setUpSynchronizerTest(); |
| config.setNodeCombiner(new UnionCombiner()); |
| sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE); |
| checkCombinedRootNotConstructed(); |
| } |
| |
| /** |
| * Tests setting a null node combiner. This should cause an exception. |
| */ |
| @Test |
| void testSetNullNodeCombiner() { |
| assertThrows(IllegalArgumentException.class, () -> config.setNodeCombiner(null)); |
| } |
| |
| /** |
| * Tests whether a sub configuration survives updates of its parent. |
| */ |
| @Test |
| void testSubConfigurationWithUpdates() { |
| final AbstractConfiguration srcConfig = setUpSubConfigTest(); |
| final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt(SUB_KEY, true); |
| assertTrue(sub.getBoolean(TEST_KEY)); |
| srcConfig.setProperty(TEST_KEY, Boolean.FALSE); |
| assertFalse(sub.getBoolean(TEST_KEY)); |
| assertFalse(config.getBoolean(SUB_KEY + '.' + TEST_KEY)); |
| } |
| |
| /** |
| * Tests if an update of a contained configuration leeds to an invalidation of the combined configuration. |
| */ |
| @Test |
| void testUpdateContainedConfiguration() { |
| final AbstractConfiguration c = setUpTestConfiguration(); |
| config.addConfiguration(c); |
| c.addProperty("test.otherTest", "yes"); |
| assertEquals("yes", config.getString("test.otherTest")); |
| listener.checkEvent(2, 0); |
| } |
| } |