/*
 * 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
 *
 *     http://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.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;

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.event.EventListenerTestImpl;
import org.apache.commons.configuration2.ex.ConversionException;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.interpol.Lookup;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import junitx.framework.ListAssert;

/**
 * Tests some basic functions of the BaseConfiguration class. Missing keys will throw Exceptions
 *
 */
public class TestBaseConfiguration {
    /** Constant for the number key. */
    static final String KEY_NUMBER = "number";

    protected static Class<?> missingElementException = NoSuchElementException.class;
    protected static Class<?> incompatibleElementException = ConversionException.class;
    protected BaseConfiguration config;

    @Before
    public void setUp() throws Exception {
        config = new BaseConfiguration();
        config.setThrowExceptionOnMissing(true);
        config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
    }

    @Test
    public void testAddProperty() throws Exception {
        Collection<Object> props = new ArrayList<>();
        props.add("one");
        props.add("two,three,four");
        props.add(new String[] {"5.1", "5.2", "5.3,5.4", "5.5"});
        props.add("six");
        config.addProperty("complex.property", props);

        Object val = config.getProperty("complex.property");
        assertTrue(val instanceof Collection);
        Collection<?> col = (Collection<?>) val;
        assertEquals(10, col.size());

        props = new ArrayList<>();
        props.add("quick");
        props.add("brown");
        props.add("fox,jumps");
        final Object[] data = {"The", props, "over,the", "lazy", "dog."};
        config.setProperty("complex.property", data);
        val = config.getProperty("complex.property");
        assertTrue(val instanceof Collection);
        col = (Collection<?>) val;
        final Iterator<?> it = col.iterator();
        final StringTokenizer tok = new StringTokenizer("The quick brown fox jumps over the lazy dog.", " ");
        while (tok.hasMoreTokens()) {
            assertTrue(it.hasNext());
            assertEquals(tok.nextToken(), it.next());
        }
        assertFalse(it.hasNext());

        config.setProperty("complex.property", null);
        assertFalse(config.containsKey("complex.property"));
    }

    /**
     * Tests cloning a BaseConfiguration.
     */
    @Test
    public void testClone() {
        for (int i = 0; i < 10; i++) {
            config.addProperty("key" + i, Integer.valueOf(i));
        }
        final BaseConfiguration config2 = (BaseConfiguration) config.clone();

        for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
            final String key = it.next();
            assertTrue("Key not found: " + key, config2.containsKey(key));
            assertEquals("Wrong value for key " + key, config.getProperty(key), config2.getProperty(key));
        }
    }

    /**
     * Tests whether interpolation works as expected after cloning.
     */
    @Test
    public void testCloneInterpolation() {
        final String keyAnswer = "answer";
        config.addProperty(keyAnswer, "The answer is ${" + KEY_NUMBER + "}.");
        config.addProperty(KEY_NUMBER, 42);
        final BaseConfiguration clone = (BaseConfiguration) config.clone();
        clone.setProperty(KEY_NUMBER, 43);
        assertEquals("Wrong interpolation in original", "The answer is 42.", config.getString(keyAnswer));
        assertEquals("Wrong interpolation in clone", "The answer is 43.", clone.getString(keyAnswer));
    }

    /**
     * Tests the clone() method if a list property is involved.
     */
    @Test
    public void testCloneListProperty() {
        final String key = "list";
        config.addProperty(key, "value1");
        config.addProperty(key, "value2");
        final BaseConfiguration config2 = (BaseConfiguration) config.clone();
        config2.addProperty(key, "value3");
        assertEquals("Wrong number of original properties", 2, config.getList(key).size());
    }

    /**
     * Tests whether a cloned configuration is decoupled from its original.
     */
    @Test
    public void testCloneModify() {
        final EventListener<ConfigurationEvent> l = new EventListenerTestImpl(config);
        config.addEventListener(ConfigurationEvent.ANY, l);
        config.addProperty("original", Boolean.TRUE);
        final BaseConfiguration config2 = (BaseConfiguration) config.clone();

        config2.addProperty("clone", Boolean.TRUE);
        assertFalse("New key appears in original", config.containsKey("clone"));
        config2.setProperty("original", Boolean.FALSE);
        assertTrue("Wrong value of original property", config.getBoolean("original"));

        assertTrue("Event listener was copied", config2.getEventListeners(ConfigurationEvent.ANY).isEmpty());
    }

    @Test
    public void testCommaSeparatedString() {
        final String prop = "hey, that's a test";
        config.setProperty("prop.string", prop);
        final List<Object> list = config.getList("prop.string");
        assertEquals("Wrong number of list elements", 2, list.size());
        assertEquals("Wrong element 1", "hey", list.get(0));
    }

    @Test
    public void testCommaSeparatedStringEscaped() {
        final String prop2 = "hey\\, that's a test";
        config.setProperty("prop.string", prop2);
        assertEquals("Wrong value", "hey, that's a test", config.getString("prop.string"));
    }

    @Test
    public void testGetBigDecimal() {
        config.setProperty("numberBigD", "123.456");
        final BigDecimal number = new BigDecimal("123.456");
        final BigDecimal defaultValue = new BigDecimal("654.321");

        assertEquals("Existing key", number, config.getBigDecimal("numberBigD"));
        assertEquals("Existing key with default value", number, config.getBigDecimal("numberBigD", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getBigDecimal("numberNotInConfig", defaultValue));
    }

    @Test(expected = ConversionException.class)
    public void testGetBigDecimalIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getBigDecimal("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetBigDecimalUnknown() {
        config.getBigDecimal("numberNotInConfig");
    }

    @Test
    public void testGetBigInteger() {
        config.setProperty("numberBigI", "1234567890");
        final BigInteger number = new BigInteger("1234567890");
        final BigInteger defaultValue = new BigInteger("654321");

        assertEquals("Existing key", number, config.getBigInteger("numberBigI"));
        assertEquals("Existing key with default value", number, config.getBigInteger("numberBigI", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getBigInteger("numberNotInConfig", defaultValue));
    }

    @Test(expected = ConversionException.class)
    public void testGetBigIntegerIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getBigInteger("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetBigIntegerUnknown() {
        config.getBigInteger("numberNotInConfig");
    }

    @Test
    public void testGetBinaryValue() {
        config.setProperty("number", "0b11111111");
        assertEquals("byte value", (byte) 0xFF, config.getByte("number"));

        config.setProperty("number", "0b1111111111111111");
        assertEquals("short value", (short) 0xFFFF, config.getShort("number"));

        config.setProperty("number", "0b11111111111111111111111111111111");
        assertEquals("int value", 0xFFFFFFFF, config.getInt("number"));

        config.setProperty("number", "0b1111111111111111111111111111111111111111111111111111111111111111");
        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getLong("number"));

        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getBigInteger("number").longValue());
    }

    @Test
    public void testGetBoolean() {
        config.setProperty("boolA", Boolean.TRUE);
        final boolean boolT = true, boolF = false;
        assertEquals("This returns true", boolT, config.getBoolean("boolA"));
        assertEquals("This returns true, not the default", boolT, config.getBoolean("boolA", boolF));
        assertEquals("This returns false(default)", boolF, config.getBoolean("boolNotInConfig", boolF));
        assertEquals("This returns true(Boolean)", Boolean.valueOf(boolT), config.getBoolean("boolA", Boolean.valueOf(boolF)));
    }

    @Test(expected = ConversionException.class)
    public void testGetBooleanIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getBoolean("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetBooleanUnknown() {
        config.getBoolean("numberNotInConfig");
    }

    @Test
    public void testGetByte() {
        config.setProperty("number", "1");
        final byte oneB = 1;
        final byte twoB = 2;
        assertEquals("This returns 1(byte)", oneB, config.getByte("number"));
        assertEquals("This returns 1(byte)", oneB, config.getByte("number", twoB));
        assertEquals("This returns 2(default byte)", twoB, config.getByte("numberNotInConfig", twoB));
        assertEquals("This returns 1(Byte)", Byte.valueOf(oneB), config.getByte("number", Byte.valueOf("2")));
    }

    @Test(expected = ConversionException.class)
    public void testGetByteIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getByte("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetByteUnknown() {
        config.getByte("numberNotInConfig");
    }

    @Test
    public void testGetDouble() {
        config.setProperty("numberD", "1.0");
        final double oneD = 1;
        final double twoD = 2;
        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD"), 0);
        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD", twoD), 0);
        assertEquals("This returns 2(default double)", twoD, config.getDouble("numberNotInConfig", twoD), 0);
        assertEquals("This returns 1(Double)", Double.valueOf(oneD), config.getDouble("numberD", Double.valueOf("2")));
    }

    @Test(expected = ConversionException.class)
    public void testGetDoubleIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getDouble("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetDoubleUnknown() {
        config.getDouble("numberNotInConfig");
    }

    @Test
    public void testGetDuration() {
        final Duration d = Duration.ofSeconds(1);
        config.setProperty("durationD", d.toString());
        final Duration oneD = Duration.ofSeconds(1);
        final Duration twoD = Duration.ofSeconds(2);
        assertEquals("This returns 1(Duration)", oneD, config.getDuration("durationD"));
        assertEquals("This returns 1(Duration)", oneD, config.getDuration("durationD", twoD));
        assertEquals("This returns 2(default Duration)", twoD, config.getDuration("numberNotInConfig", twoD));
        assertEquals("This returns 1(Duration)", oneD, config.getDuration("durationD", twoD));
    }

    @Test(expected = ConversionException.class)
    public void testGetDurationIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getDuration("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetDurationUnknown() {
        config.getDuration("numberNotInConfig");
    }

    @Test
    public void testGetEnum() {
        config.setProperty("testEnum", EnumFixture.SMALLTALK.name());
        config.setProperty("testBadEnum", "This is not an enum value.");
        final EnumFixture enum1 = EnumFixture.SMALLTALK;
        final EnumFixture defaultValue = EnumFixture.JAVA;
        //
        assertEquals("Existing key", enum1, config.getEnum("testEnum", EnumFixture.class));
        assertEquals("Existing key with default value", enum1, config.getEnum("testEnum", EnumFixture.class, defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getEnum("stringNotInConfig", EnumFixture.class, defaultValue));
        //
        try {
            config.getEnum("testBadEnum", EnumFixture.class);
            fail("Expected " + ConversionException.class);
        } catch (final ConversionException e) {
            // expected
        }
        //
        try {
            config.getEnum("testBadEnum", EnumFixture.class, defaultValue);
            fail("Expected " + ConversionException.class);
        } catch (final ConversionException e) {
            // expected
        }
    }

    @Test
    public void testGetFloat() {
        config.setProperty("numberF", "1.0");
        final float oneF = 1;
        final float twoF = 2;
        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF"), 0);
        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF", twoF), 0);
        assertEquals("This returns 2(default float)", twoF, config.getFloat("numberNotInConfig", twoF), 0);
        assertEquals("This returns 1(Float)", Float.valueOf(oneF), config.getFloat("numberF", Float.valueOf("2")));
    }

    @Test(expected = ConversionException.class)
    public void testGetFloatIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getFloat("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetFloatUnknown() {
        config.getFloat("numberNotInConfig");
    }

    @Test
    public void testGetHexadecimalValue() {
        config.setProperty("number", "0xFF");
        assertEquals("byte value", (byte) 0xFF, config.getByte("number"));

        config.setProperty("number", "0xFFFF");
        assertEquals("short value", (short) 0xFFFF, config.getShort("number"));

        config.setProperty("number", "0xFFFFFFFF");
        assertEquals("int value", 0xFFFFFFFF, config.getInt("number"));

        config.setProperty("number", "0xFFFFFFFFFFFFFFFF");
        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getLong("number"));

        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getBigInteger("number").longValue());
    }

    @Test
    public void testGetInterpolatedList() {
        config.addProperty("number", "1");
        config.addProperty("array", "${number}");
        config.addProperty("array", "${number}");

        final List<String> list = new ArrayList<>();
        list.add("1");
        list.add("1");

        ListAssert.assertEquals("'array' property", list, config.getList("array"));
    }

    @Test
    public void testGetInterpolatedPrimitives() {
        config.addProperty("number", "1");
        config.addProperty("value", "${number}");

        config.addProperty("boolean", "true");
        config.addProperty("booleanValue", "${boolean}");

        // primitive types
        assertTrue("boolean interpolation", config.getBoolean("booleanValue"));
        assertEquals("byte interpolation", 1, config.getByte("value"));
        assertEquals("short interpolation", 1, config.getShort("value"));
        assertEquals("int interpolation", 1, config.getInt("value"));
        assertEquals("long interpolation", 1, config.getLong("value"));
        assertEquals("float interpolation", 1, config.getFloat("value"), 0);
        assertEquals("double interpolation", 1, config.getDouble("value"), 0);

        // primitive wrappers
        assertEquals("Boolean interpolation", Boolean.TRUE, config.getBoolean("booleanValue", null));
        assertEquals("Byte interpolation", Byte.valueOf("1"), config.getByte("value", null));
        assertEquals("Short interpolation", Short.valueOf("1"), config.getShort("value", null));
        assertEquals("Integer interpolation", Integer.valueOf("1"), config.getInteger("value", null));
        assertEquals("Long interpolation", Long.valueOf("1"), config.getLong("value", null));
        assertEquals("Float interpolation", Float.valueOf("1"), config.getFloat("value", null));
        assertEquals("Double interpolation", Double.valueOf("1"), config.getDouble("value", null));

        assertEquals("BigInteger interpolation", new BigInteger("1"), config.getBigInteger("value", null));
        assertEquals("BigDecimal interpolation", new BigDecimal("1"), config.getBigDecimal("value", null));
    }

    /**
     * Tests accessing and manipulating the interpolator object.
     */
    @Test
    public void testGetInterpolator() {
        InterpolationTestHelper.testGetInterpolator(config);
    }

    @Test
    public void testGetList() {
        config.addProperty("number", "1");
        config.addProperty("number", "2");
        final List<Object> list = config.getList("number");
        assertNotNull("The list is null", list);
        assertEquals("List size", 2, list.size());
        assertTrue("The number 1 is missing from the list", list.contains("1"));
        assertTrue("The number 2 is missing from the list", list.contains("2"));
    }

    @Test
    public void testGetLong() {
        config.setProperty("numberL", "1");
        final long oneL = 1;
        final long twoL = 2;
        assertEquals("This returns 1(long)", oneL, config.getLong("numberL"));
        assertEquals("This returns 1(long)", oneL, config.getLong("numberL", twoL));
        assertEquals("This returns 2(default long)", twoL, config.getLong("numberNotInConfig", twoL));
        assertEquals("This returns 1(Long)", Long.valueOf(oneL), config.getLong("numberL", Long.valueOf("2")));
    }

    @Test(expected = ConversionException.class)
    public void testGetLongIncompatibleTypes() {
        config.setProperty("test.empty", "");
        config.getLong("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetLongUnknown() {
        config.getLong("numberNotInConfig");
    }

    @Test
    public void testGetProperty() {
        /* should be empty and return null */
        assertNull("This returns null", config.getProperty("foo"));

        /* add a real value, and get it two different ways */
        config.setProperty("number", "1");
        assertEquals("This returns '1'", config.getProperty("number"), "1");
        assertEquals("This returns '1'", config.getString("number"), "1");
    }

    @Test
    public void testGetShort() {
        config.setProperty("numberS", "1");
        final short oneS = 1;
        final short twoS = 2;
        assertEquals("This returns 1(short)", oneS, config.getShort("numberS"));
        assertEquals("This returns 1(short)", oneS, config.getShort("numberS", twoS));
        assertEquals("This returns 2(default short)", twoS, config.getShort("numberNotInConfig", twoS));
        assertEquals("This returns 1(Short)", Short.valueOf(oneS), config.getShort("numberS", Short.valueOf("2")));
    }

    @Test(expected = ConversionException.class)
    public void testGetShortIncompatibleType() {
        config.setProperty("test.empty", "");
        config.getShort("test.empty");
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetShortUnknown() {
        config.getShort("numberNotInConfig");
    }

    @Test
    public void testGetString() {
        config.setProperty("testString", "The quick brown fox");
        final String string = "The quick brown fox";
        final String defaultValue = "jumps over the lazy dog";

        assertEquals("Existing key", string, config.getString("testString"));
        assertEquals("Existing key with default value", string, config.getString("testString", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getString("stringNotInConfig", defaultValue));
    }

    /**
     * Tests that the first scalar of a list is returned.
     */
    @Test
    public void testGetStringForListValue() {
        config.addProperty("number", "1");
        config.addProperty("number", "2");
        assertEquals("Wrong result", "1", config.getString("number"));
    }

    @Test(expected = NoSuchElementException.class)
    public void testGetStringUnknown() {
        config.getString("stringNotInConfig");
    }

    /**
     * Tests whether a {@code ConfigurationInterpolator} can be created and installed.
     */
    @Test
    public void testInstallInterpolator() {
        final Lookup prefixLookup = EasyMock.createMock(Lookup.class);
        final Lookup defLookup = EasyMock.createMock(Lookup.class);
        EasyMock.replay(prefixLookup, defLookup);
        final Map<String, Lookup> prefixLookups = new HashMap<>();
        prefixLookups.put("test", prefixLookup);
        final List<Lookup> defLookups = new ArrayList<>();
        defLookups.add(defLookup);
        config.installInterpolator(prefixLookups, defLookups);
        final ConfigurationInterpolator interpolator = config.getInterpolator();
        assertEquals("Wrong prefix lookups", prefixLookups, interpolator.getLookups());
        final List<Lookup> defLookups2 = interpolator.getDefaultLookups();
        assertEquals("Wrong number of default lookups", 2, defLookups2.size());
        assertSame("Wrong default lookup 1", defLookup, defLookups2.get(0));
        final String var = "testVariable";
        final Object value = 42;
        config.addProperty(var, value);
        assertEquals("Wrong lookup result", value, defLookups2.get(1).lookup(var));
    }

    /**
     * Tests obtaining a configuration with all variables replaced by their actual values.
     */
    @Test
    public void testInterpolatedConfiguration() {
        InterpolationTestHelper.testInterpolatedConfiguration(config);
    }

    @Test
    public void testInterpolation() {
        InterpolationTestHelper.testInterpolation(config);
    }

    /**
     * Tests interpolation of constant values.
     */
    @Test
    public void testInterpolationConstants() {
        InterpolationTestHelper.testInterpolationConstants(config);
    }

    /**
     * Tests interpolation of environment properties.
     */
    @Test
    public void testInterpolationEnvironment() {
        InterpolationTestHelper.testInterpolationEnvironment(config);
    }

    /**
     * Tests whether a variable can be escaped, so that it won't be interpolated.
     */
    @Test
    public void testInterpolationEscaped() {
        InterpolationTestHelper.testInterpolationEscaped(config);
    }

    /**
     * Tests interpolation with localhost values.
     */
    @Test
    public void testInterpolationLocalhost() {
        InterpolationTestHelper.testInterpolationLocalhost(config);
    }

    @Test
    public void testInterpolationLoop() {
        InterpolationTestHelper.testInterpolationLoop(config);
    }

    /**
     * Tests interpolation when a subset configuration is involved.
     */
    @Test
    public void testInterpolationSubset() {
        InterpolationTestHelper.testInterpolationSubset(config);
    }

    /**
     * Tests interpolation of system properties.
     */
    @Test
    public void testInterpolationSystemProperties() {
        InterpolationTestHelper.testInterpolationSystemProperties(config);
    }

    /**
     * Tests interpolation when the referred property is not found.
     */
    @Test
    public void testInterpolationUnknownProperty() {
        InterpolationTestHelper.testInterpolationUnknownProperty(config);
    }

    @Test
    public void testMultipleInterpolation() {
        InterpolationTestHelper.testMultipleInterpolation(config);
    }

    /**
     * Tests whether property access is possible without a {@code ConfigurationInterpolator}.
     */
    @Test
    public void testNoInterpolator() {
        config.setProperty("test", "${value}");
        config.setInterpolator(null);
        assertEquals("Wrong result", "${value}", config.getString("test"));
    }

    /**
     * Tests if conversion between number types is possible.
     */
    @Test
    public void testNumberConversions() {
        config.setProperty(KEY_NUMBER, Integer.valueOf(42));
        assertEquals("Wrong int returned", 42, config.getInt(KEY_NUMBER));
        assertEquals("Wrong long returned", 42L, config.getLong(KEY_NUMBER));
        assertEquals("Wrong byte returned", (byte) 42, config.getByte(KEY_NUMBER));
        assertEquals("Wrong float returned", 42.0f, config.getFloat(KEY_NUMBER), 0.01f);
        assertEquals("Wrong double returned", 42.0, config.getDouble(KEY_NUMBER), 0.001);

        assertEquals("Wrong Long returned", Long.valueOf(42L), config.getLong(KEY_NUMBER, null));
        assertEquals("Wrong BigInt returned", new BigInteger("42"), config.getBigInteger(KEY_NUMBER));
        assertEquals("Wrong DigDecimal returned", new BigDecimal("42"), config.getBigDecimal(KEY_NUMBER));
    }

    @Test
    public void testPropertyAccess() {
        config.clearProperty("prop.properties");
        config.setProperty("prop.properties", "");
        assertEquals("This returns an empty Properties object", config.getProperties("prop.properties"), new Properties());
        config.clearProperty("prop.properties");
        config.setProperty("prop.properties", "foo=bar, baz=moo, seal=clubber");

        final Properties p = new Properties();
        p.setProperty("foo", "bar");
        p.setProperty("baz", "moo");
        p.setProperty("seal", "clubber");
        assertEquals("This returns a filled in Properties object", config.getProperties("prop.properties"), p);
    }

    /**
     * Tests whether a {@code ConfigurationInterpolator} can be set.
     */
    @Test
    public void testSetInterpolator() {
        final ConfigurationInterpolator interpolator = EasyMock.createMock(ConfigurationInterpolator.class);
        EasyMock.replay(interpolator);
        config.setInterpolator(interpolator);
        assertSame("Interpolator not set", interpolator, config.getInterpolator());
    }

    /**
     * Tests the specific size() implementation.
     */
    @Test
    public void testSize() {
        final int count = 16;
        for (int i = 0; i < count; i++) {
            config.addProperty("key" + i, "value" + i);
        }
        assertEquals("Wrong size", count, config.size());
    }

    @Test
    public void testSubset() {
        /*
         * test subset : assure we don't reprocess the data elements when generating the subset
         */

        final String prop = "hey, that's a test";
        final String prop2 = "hey\\, that's a test";
        config.setProperty("prop.string", prop2);
        config.setProperty("property.string", "hello");

        Configuration subEprop = config.subset("prop");

        assertEquals("Returns the full string", prop, subEprop.getString("string"));
        assertEquals("Wrong list size", 1, subEprop.getList("string").size());

        Iterator<String> it = subEprop.getKeys();
        it.next();
        assertFalse(it.hasNext());

        subEprop = config.subset("prop.");
        it = subEprop.getKeys();
        assertFalse(it.hasNext());
    }

    @Test
    public void testThrowExceptionOnMissing() {
        assertTrue("Throw Exception Property is not set!", config.isThrowExceptionOnMissing());
    }
}
