You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2009/03/01 22:14:06 UTC

svn commit: r749111 - in /commons/proper/configuration/branches/configuration2_experimental/src: main/java/org/apache/commons/configuration2/base/ test/java/org/apache/commons/configuration2/base/

Author: oheger
Date: Sun Mar  1 21:14:06 2009
New Revision: 749111

URL: http://svn.apache.org/viewvc?rev=749111&view=rev
Log:
Initial implementation of a configuration source that keeps its data in memory.
(A major part of the code could be copied from InMemoryConfiguration.)

Added:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java   (with props)
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java   (with props)

Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java?rev=749111&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java Sun Mar  1 21:14:06 2009
@@ -0,0 +1,81 @@
+/*
+ * 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.base;
+
+import org.apache.commons.configuration2.expr.ConfigurationNodeHandler;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.apache.commons.configuration2.tree.DefaultConfigurationNode;
+
+/**
+ * <p>
+ * A specialized implementation of {@code HierarchicalConfigurationSource} that
+ * operates on a structure of {@link ConfigurationNode} objects that are hold in
+ * memory.
+ * </p>
+ * <p>
+ * Implementation note: an {@code InMemoryConfigurationSource} can be queried
+ * concurrently by multiple threads. However, if updates are performed, client
+ * code must ensure proper synchronization.
+ * </p>
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class InMemoryConfigurationSource extends
+        AbstractHierarchicalConfigurationSource<ConfigurationNode>
+{
+    /** Stores the root configuration node. */
+    private volatile ConfigurationNode rootNode;
+
+    /**
+     * Creates a new instance of {@code InMemoryConfigurationSource}.
+     */
+    public InMemoryConfigurationSource()
+    {
+        super(new ConfigurationNodeHandler());
+        rootNode = new DefaultConfigurationNode();
+    }
+
+    /**
+     * Returns a reference to the root node.
+     *
+     * @return the root configuration node
+     */
+    public ConfigurationNode getRootNode()
+    {
+        return rootNode;
+    }
+
+    /**
+     * Sets the root node for this configuration source. An {@code
+     * InMemoryConfigurationSource} allows changing its root node. This will
+     * change the whole content of the source.
+     *
+     * @param root the new root node (must not be <b>null</b>)
+     * @throws IllegalArgumentException if the root node is <b>null</b>
+     */
+    @Override
+    public void setRootNode(ConfigurationNode root)
+    {
+        if (root == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+
+        rootNode = root;
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/InMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java?rev=749111&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java Sun Mar  1 21:14:06 2009
@@ -0,0 +1,792 @@
+/*
+ * 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.base;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.expr.NodeList;
+import org.apache.commons.configuration2.expr.NodeVisitorAdapter;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.apache.commons.configuration2.tree.DefaultConfigurationNode;
+
+/**
+ * Test class for {@link InMemoryConfigurationSource}. This class also tests
+ * functionality provided by the base class.
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class TestInMemoryConfigurationSource extends TestCase
+{
+    /** An array with the names of the test TABLES. */
+    private static final String[] TABLES = {
+            "users", "documents"
+    };
+
+    /** An array with the names of the table FIELDS. */
+    private static final String[][] FIELDS = {
+            {
+                    "uid", "uname", "firstName", "lastName", "email"
+            }, {
+                    "docid", "name", "creationDate", "authorID", "version"
+            }
+    };
+
+    /** An array with flags whether the test TABLES are system TABLES. */
+    private static final Boolean[] SYS_TABLES = {
+            Boolean.TRUE, Boolean.FALSE
+    };
+
+    /** The source to be tested. */
+    private InMemoryConfigurationSource source;
+
+    /**
+     * Initializes the configuration source with the following structure: tables
+     * table name fields field name field name
+     */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+
+        ConfigurationNode nodeTables = createNode("tables", null);
+        for (int i = 0; i < TABLES.length; i++)
+        {
+            ConfigurationNode nodeTable = createNode("table", null);
+            nodeTables.addChild(nodeTable);
+            ConfigurationNode nodeName = createNode("name", TABLES[i]);
+            nodeTable.addChild(nodeName);
+            ConfigurationNode attrType = createNode("sysTab", SYS_TABLES[i]);
+            nodeTable.addAttribute(attrType);
+            ConfigurationNode nodeFields = createNode("fields", null);
+            nodeTable.addChild(nodeFields);
+
+            for (int j = 0; j < FIELDS[i].length; j++)
+            {
+                nodeFields.addChild(createFieldNode(FIELDS[i][j]));
+            }
+        }
+
+        source = new InMemoryConfigurationSource();
+        source.getRootNode().addChild(nodeTables);
+    }
+
+    /**
+     * Helper method for creating a field node with its children.
+     *
+     * @param name the name of the field
+     * @return the field node
+     */
+    private static ConfigurationNode createFieldNode(String name)
+    {
+        ConfigurationNode fld = createNode("field", null);
+        fld.addChild(createNode("name", name));
+        return fld;
+    }
+
+    /**
+     * Helper method for creating a configuration node.
+     *
+     * @param name the node's name
+     * @param value the node's value
+     * @return the new node
+     */
+    private static ConfigurationNode createNode(String name, Object value)
+    {
+        ConfigurationNode node = new DefaultConfigurationNode(name);
+        node.setValue(value);
+        return node;
+    }
+
+    /**
+     * Returns the number of nodes that are stored in the test source.
+     * Optionally only the nodes with a value are counted.
+     *
+     * @param withValue if true, only the nodes with a value are taken into
+     *        account
+     * @return the number of nodes
+     */
+    private static int nodeCount(boolean withValue)
+    {
+        int tabNodes = 2; // each table has a name and system flag
+        if (!withValue)
+        {
+            tabNodes += 2; // also table and fields node
+        }
+        int count = TABLES.length * tabNodes;
+
+        int fieldNodes = 1; // the name of each field
+        if (!withValue)
+        {
+            fieldNodes += 1; // also the field node itself
+        }
+        for (int i = 0; i < FIELDS.length; i++)
+        {
+            count += FIELDS[i].length * fieldNodes; // the number of fields
+        }
+        return count;
+    }
+
+    /**
+     * Helper method for determining the number of values of a given property.
+     * This method expects that the property value is a collection.
+     *
+     * @param key the property key
+     * @return the number of values stored for this property
+     */
+    private int valueCount(String key)
+    {
+        return ((Collection<?>) source.getProperty(key)).size();
+    }
+
+    /**
+     * Tests setting a new root node.
+     */
+    public void testSetRootNode()
+    {
+        DefaultConfigurationNode node = new DefaultConfigurationNode();
+        source.setRootNode(node);
+        assertSame("Root node was not changed", node, source.getRootNode());
+    }
+
+    /**
+     * Tests setting the root node to null. This should cause an exception.
+     */
+    public void testSetRootNodeNull()
+    {
+        try
+        {
+            source.setRootNode(null);
+            fail("Could set null root node!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests visiting the root node.
+     */
+    public void testVisitRoot()
+    {
+        CountVisitor visitor = new CountVisitor();
+        source.visit(null, visitor);
+        visitor.check(nodeCount(false));
+    }
+
+    /**
+     * Tests visiting only a part of the node structure.
+     */
+    public void testVisitPartly()
+    {
+        NodeList<ConfigurationNode> list = source
+                .find("tables.table(0).fields.field(1)");
+        assertEquals("Wrong size", 1, list.size());
+        CountVisitor visitor = new CountVisitor();
+        source.visit(list.getNode(0), visitor);
+        visitor.check(2);
+    }
+
+    /**
+     * Tests the isEmpty() method when the source contains data.
+     */
+    public void testIsEmptyDataAvailable()
+    {
+        assertFalse("Source is empty", source.isEmpty());
+    }
+
+    /**
+     * Tests isEmpty() when the source does not contain data.
+     */
+    public void testIsEmptyNoData()
+    {
+        source.setRootNode(new DefaultConfigurationNode());
+        assertTrue("Source not empty", source.isEmpty());
+    }
+
+    /**
+     * Tests the size of the source.
+     */
+    public void testSize()
+    {
+        assertEquals("Wrong size", nodeCount(true), source.size());
+    }
+
+    /**
+     * Tests the size of the source when there is only an empty root node.
+     */
+    public void testSizeEmpty()
+    {
+        source.setRootNode(new DefaultConfigurationNode());
+        assertEquals("Wrong size", 0, source.size());
+    }
+
+    /**
+     * Tests accessing properties.
+     */
+    public void testGetProperty()
+    {
+        Object prop = source.getProperty("tables.table(0).fields.field.name");
+        assertNotNull("No field names found for tab 0", prop);
+        assertTrue("No multiple field names for tab 0",
+                prop instanceof Collection);
+        assertEquals("Wrong number of field names for tab 0", 5,
+                ((Collection<?>) prop).size());
+
+        prop = source.getProperty("tables.table.fields.field.name");
+        assertNotNull("No field names found", prop);
+        assertTrue("No multiple field names", prop instanceof Collection);
+        assertEquals("Wrong number of field names", 10, ((Collection<?>) prop)
+                .size());
+
+        prop = source.getProperty("tables.table.fields.field(3).name");
+        assertNotNull("No names for field 3 found", prop);
+        assertTrue("No multiple field 3 names", prop instanceof Collection);
+        assertEquals("Wrong number of field 3 names", 2, ((Collection<?>) prop)
+                .size());
+
+        prop = source.getProperty("tables.table(1).fields.field(2).name");
+        assertEquals("Wrong field name", "creationDate", prop.toString());
+    }
+
+    /**
+     * Tests getProperty() for an unknown property.
+     */
+    public void testGetPropertyUnknown()
+    {
+        assertNull("Got value for unknown property", source
+                .getProperty("TABLES.table.resultset"));
+    }
+
+    /**
+     * Tests getProperty() for a property that is stored, but has no value.
+     */
+    public void testGetPropertyUndefined()
+    {
+        assertNull("Got value for undefined property", source
+                .getProperty("tables.table.fields.field"));
+    }
+
+    /**
+     * Tests setting property values.
+     */
+    public void testSetProperty()
+    {
+        source.setProperty("tables.table(0).name", "resources");
+        assertEquals("Name not changed", "resources", source
+                .getProperty("tables.table(0).name"));
+        source.setProperty("tables.table.name", Arrays.asList(new String[] {
+                "tab1", "tab2"
+        }));
+        assertEquals("Tab 1 name not changed", "tab1", source
+                .getProperty("tables.table(0).name"));
+        assertEquals("Tab 2 name not changed", "tab2", source
+                .getProperty("tables.table(1).name"));
+
+        final Integer[] testValues = new Integer[] {
+                2, 4, 8, 16
+        };
+        source.setProperty("test.items.item", Arrays.asList(testValues));
+        List<?> values = (List<?>) source.getProperty("test.items.item");
+        assertEquals("Wrong number of test items", testValues.length, values
+                .size());
+        assertEquals("Wrong test item", testValues[2], source
+                .getProperty("test.items.item(2)"));
+        final Integer newValue = 6;
+        source.setProperty("test.items.item(2)", newValue);
+        assertEquals("Item not changed", newValue, source
+                .getProperty("test.items.item(2)"));
+        final Integer[] moreValues = new Integer[] {
+                7, 9, 11
+        };
+        source.setProperty("test.items.item(2)", Arrays.asList(moreValues));
+        values = (List<?>) source.getProperty("test.items.item");
+        assertEquals("Wrong number of added items", 6, values.size());
+
+        source.setProperty("test", Boolean.TRUE);
+        source.setProperty("test.items", "01/01/05");
+        values = (List<?>) source.getProperty("test.items.item");
+        assertEquals("Items were changed", 6, values.size());
+        assertEquals("Wrong boolean", Boolean.TRUE, source.getProperty("test"));
+        assertEquals("Wrong string", "01/01/05", source
+                .getProperty("test.items"));
+
+        final Integer replaceValue = 42;
+        source.setProperty("test.items.item", replaceValue);
+        assertEquals("Items not replaced", replaceValue, source
+                .getProperty("test.items.item"));
+    }
+
+    /**
+     * Tests removing properties.
+     */
+    public void testClearProperty()
+    {
+        source.clearProperty("tables.table(0).fields.field(0).name");
+        assertEquals("Field name not removed", "uname", source
+                .getProperty("tables.table(0).fields.field(0).name"));
+        source.clearProperty("tables.table(0).name");
+        assertFalse("Table name still present", source
+                .containsKey("tables.table(0).name"));
+        assertEquals("Wrong field name", "firstName", source
+                .getProperty("tables.table(0).fields.field(1).name"));
+        assertEquals("Wrong table name", "documents", source
+                .getProperty("tables.table.name"));
+        source.clearProperty("tables.table");
+        assertEquals("Table name affected", "documents", source
+                .getProperty("tables.table.name"));
+
+        source.addProperty("test", "first");
+        source.addProperty("test.level", "second");
+        source.clearProperty("test");
+        assertEquals("Sub property was changed", "second", source
+                .getProperty("test.level"));
+        assertFalse("Property not removed", source.containsKey("test"));
+    }
+
+    /**
+     * Tests clearing whole property trees.
+     */
+    public void testClearTree()
+    {
+        Object prop = source.getProperty("tables.table(0).fields.field.name");
+        assertNotNull("Property not found", prop);
+        source.clearTree("tables.table(0).fields.field(3)");
+        prop = source.getProperty("tables.table(0).fields.field.name");
+        assertNotNull("Property not found (2)", prop);
+        assertTrue("Not multiple values (1)", prop instanceof Collection);
+        assertEquals("Element not removed", 4, ((Collection<?>) prop).size());
+
+        source.clearTree("tables.table(0).fields");
+        assertNull("Sub property still found", source
+                .getProperty("tables.table(0).fields.field.name"));
+        prop = source.getProperty("tables.table.fields.field.name");
+        assertNotNull("Property not found (3)", prop);
+        assertTrue("Not multiple values (2)", prop instanceof Collection);
+        assertEquals("Wrong number of elements", 5, ((Collection<?>) prop)
+                .size());
+
+        source.clearTree("tables.table(1)");
+        assertNull("Still found table names", source
+                .getProperty("tables.table.fields.field.name"));
+    }
+
+    /**
+     * Tests removing more complex node structures.
+     */
+    public void testClearTreeComplex()
+    {
+        final int count = 5;
+        // create the structure
+        for (int idx = 0; idx < count; idx++)
+        {
+            source.addProperty("indexList.index(-1)[@default]", Boolean.FALSE);
+            source.addProperty("indexList.index[@name]", "test" + idx);
+            source.addProperty("indexList.index.dir", "testDir" + idx);
+        }
+        assertEquals("Wrong number of nodes", count,
+                valueCount("indexList.index[@name]"));
+
+        // Remove a sub tree
+        boolean found = false;
+        for (int idx = 0; true; idx++)
+        {
+            String name = (String) source.getProperty("indexList.index(" + idx
+                    + ")[@name]");
+            if (name == null)
+            {
+                break;
+            }
+            if ("test3".equals(name))
+            {
+                assertEquals("Wrong dir", "testDir3", source
+                        .getProperty("indexList.index(" + idx + ").dir"));
+                source.clearTree("indexList.index(" + idx + ")");
+                found = true;
+            }
+        }
+        assertTrue("Key to remove not found", found);
+        assertEquals("Wrong number of nodes after remove", count - 1,
+                valueCount("indexList.index[@name]"));
+        assertEquals("Wrong number of dir nodes after remove", count - 1,
+                valueCount("indexList.index.dir"));
+
+        // Verify
+        for (int idx = 0; true; idx++)
+        {
+            String name = (String) source.getProperty("indexList.index(" + idx
+                    + ")[@name]");
+            if (name == null)
+            {
+                break;
+            }
+            if ("test3".equals(name))
+            {
+                fail("Key was not removed!");
+            }
+        }
+    }
+
+    /**
+     * Tests the clearTree() method on a hierarchical structure of nodes.
+     */
+    public void testClearTreeHierarchy()
+    {
+        source.addProperty("a.b.c", "c");
+        source.addProperty("a.b.c.d", "d");
+        source.addProperty("a.b.c.d.e", "e");
+        source.clearTree("a.b.c");
+        assertFalse("Property not removed", source.containsKey("a.b.c"));
+        assertFalse("Sub property not removed", source.containsKey("a.b.c.d"));
+    }
+
+    /**
+     * Tests for the containsKey() method.
+     */
+    public void testContainsKey()
+    {
+        assertTrue("No table name 1", source
+                .containsKey("tables.table(0).name"));
+        assertTrue("No table name 2", source
+                .containsKey("tables.table(1).name"));
+        assertFalse("Got name 3", source.containsKey("tables.table(2).name"));
+
+        assertTrue("No field name", source
+                .containsKey("tables.table(0).fields.field.name"));
+        assertFalse("Got a field", source
+                .containsKey("tables.table(0).fields.field"));
+        source.clearTree("tables.table(0).fields");
+        assertFalse("Got fields after remove", source
+                .containsKey("tables.table(0).fields.field.name"));
+
+        assertTrue("No more names", source
+                .containsKey("tables.table.fields.field.name"));
+    }
+
+    /**
+     * Tests the keys returned by the configuration source.
+     */
+    public void testGetKeys()
+    {
+        List<String> keys = new ArrayList<String>();
+        for (Iterator<String> it = source.getKeys(); it.hasNext();)
+        {
+            keys.add(it.next());
+        }
+
+        assertEquals("Wrong number of keys", 3, keys.size());
+        assertTrue("No table names", keys.contains("tables.table.name"));
+        assertTrue("No field names", keys
+                .contains("tables.table.fields.field.name"));
+        assertTrue("No sys tab", keys.contains("tables.table[@sysTab]"));
+    }
+
+    /**
+     * Tests whether keys are returned in the order they are added.
+     */
+    public void testGetKeysOrdered()
+    {
+        source.addProperty("order.key1", "value1");
+        source.addProperty("order.key2", "value2");
+        source.addProperty("order.key3", "value3");
+
+        Iterator<String> it = source.getKeys("order");
+        assertEquals("1st key", "order.key1", it.next());
+        assertEquals("2nd key", "order.key2", it.next());
+        assertEquals("3rd key", "order.key3", it.next());
+    }
+
+    /**
+     * Tests the getKeys() method with a string prefix.
+     */
+    public void testGetKeysString()
+    {
+        // add some more properties to make it more interesting
+        source.addProperty("tables.table(0).fields.field(1).type", "VARCHAR");
+        source.addProperty("tables.table(0)[@type]", "system");
+        source.addProperty("tables.table(0).size", "42");
+        source.addProperty("tables.table(0).fields.field(0).size", "128");
+        source.addProperty("connections.connection.param.url", "url1");
+        source.addProperty("connections.connection.param.user", "me");
+        source.addProperty("connections.connection.param.pwd", "secret");
+        source.addProperty("connections.connection(-1).param.url", "url2");
+        source.addProperty("connections.connection(1).param.user", "guest");
+
+        checkKeys("tables.table(1)", new String[] {
+                "name", "fields.field.name", "tables.table(1)[@sysTab]"
+        });
+        checkKeys("tables.table(0)", new String[] {
+                "name", "fields.field.name", "tables.table(0)[@type]",
+                "tables.table(0)[@sysTab]", "size", "fields.field.type",
+                "fields.field.size"
+        });
+        checkKeys("connections.connection(0).param", new String[] {
+                "url", "user", "pwd"
+        });
+        checkKeys("connections.connection(1).param", new String[] {
+                "url", "user"
+        });
+    }
+
+    /**
+     * Tests getKeys() with a prefix when the prefix matches exactly a key.
+     */
+    public void testGetKeysWithKeyAsPrefix()
+    {
+        source.addProperty("order.key1", "value1");
+        source.addProperty("order.key2", "value2");
+        Iterator<String> it = source.getKeys("order.key1");
+        assertTrue("no key found", it.hasNext());
+        assertEquals("1st key", "order.key1", it.next());
+        assertFalse("more keys than expected", it.hasNext());
+    }
+
+    /**
+     * Tests getKeys() with a prefix when the prefix matches exactly a key, and
+     * there are multiple keys starting with this prefix.
+     */
+    public void testGetKeysWithKeyAsPrefixMultiple()
+    {
+        source.addProperty("order.key1", "value1");
+        source.addProperty("order.key1.test", "value2");
+        source.addProperty("order.key1.test.complex", "value2");
+        Iterator<String> it = source.getKeys("order.key1");
+        assertEquals("Wrong key 1", "order.key1", it.next());
+        assertEquals("Wrong key 2", "order.key1.test", it.next());
+        assertEquals("Wrong key 3", "order.key1.test.complex", it.next());
+        assertFalse("More keys than expected", it.hasNext());
+    }
+
+    /**
+     * Helper method for testing the getKeys(String) method.
+     *
+     * @param prefix the key to pass into getKeys()
+     * @param expected the expected result
+     */
+    private void checkKeys(String prefix, String[] expected)
+    {
+        Set<String> values = new HashSet<String>();
+        for (String exp : expected)
+        {
+            values.add((exp.startsWith(prefix)) ? exp : prefix + "." + exp);
+        }
+
+        Iterator<String> itKeys = source.getKeys(prefix);
+        while (itKeys.hasNext())
+        {
+            String key = itKeys.next();
+            if (!values.contains(key))
+            {
+                fail("Found unexpected key: " + key);
+            }
+            else
+            {
+                values.remove(key);
+            }
+        }
+
+        assertTrue("Remaining keys " + values, values.isEmpty());
+    }
+
+    /**
+     * Tests adding different new properties.
+     */
+    public void testAddProperty()
+    {
+        source.addProperty("tables.table(0).fields.field(-1).name", "phone");
+        assertEquals("Field phone not added", 6,
+                valueCount("tables.table(0).fields.field.name"));
+
+        source.addProperty("tables.table(0).fields.field.name", "fax");
+        Object prop = source.getProperty("tables.table.fields.field(5).name");
+        assertTrue("Not multiple values", prop instanceof List);
+        List<?> list = (List<?>) prop;
+        assertEquals("Wrong element 0", "phone", list.get(0));
+        assertEquals("Wrong element 1", "fax", list.get(1));
+
+        source.addProperty("tables.table(-1).name", "config");
+        prop = source.getProperty("tables.table.name");
+        assertTrue("Not multiple table names", prop instanceof Collection);
+        assertEquals("Wrong number of tables", 3, ((Collection<?>) prop).size());
+        source.addProperty("tables.table(2).fields.field(0).name", "cid");
+        source.addProperty("tables.table(2).fields.field(-1).name", "confName");
+        assertEquals("Wrong number of fields in new table", 2,
+                valueCount("tables.table(2).fields.field.name"));
+        assertEquals("Wrong name of new field", "confName", source
+                .getProperty("tables.table(2).fields.field(1).name"));
+
+        source.addProperty("connection.user", "scott");
+        source.addProperty("connection.passwd", "tiger");
+        assertEquals("Wrong password", "tiger", source
+                .getProperty("connection.passwd"));
+    }
+
+    /**
+     * Tests addProperty() when an invalid key is passed in. This should cause
+     * an exception.
+     */
+    public void testAddPropertyInvalidKey()
+    {
+        try
+        {
+            source.addProperty(".", "InvalidKey");
+            fail("Could add invalid key!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests counting the values stored for a property key.
+     */
+    public void testValueCount()
+    {
+        assertEquals("Wrong fields in tab 0", 5, source
+                .valueCount("tables.table(0).fields.field.name"));
+        assertEquals("Wrong fields in tab 1", 5, source
+                .valueCount("tables.table(1).fields.field.name"));
+        assertEquals("Wrong table names", 2, source
+                .valueCount("tables.table.name"));
+        assertEquals("Wrong name count for tab ", 1, source
+                .valueCount("tables.table(0).name"));
+    }
+
+    /**
+     * Tests the valueCount() method when the passed in key does not exist.
+     */
+    public void testValueCountNonExisting()
+    {
+        assertEquals("Wrong value for non existing key", 0, source
+                .valueCount("non.existing.key"));
+    }
+
+    /**
+     * Tests the valueCount() method when the passed in key references a node
+     * that does not have a value.
+     */
+    public void testValueCountUndefined()
+    {
+        assertEquals("Wrong result for key without a value", 0, source
+                .valueCount("tables.table(1).fields.field(1)"));
+    }
+
+    /**
+     * Tests querying the number of values for attributes with multiple values.
+     */
+    public void testValueCountAttributesMultipleValues()
+    {
+        source.addProperty("tables.table(0)[@mode]", "test");
+        source.addProperty("tables.table(0)[@mode]", "production");
+        source.addProperty("tables.table(1)[@mode]", "staging");
+        assertEquals("Wrong value count", 3, source
+                .valueCount("tables.table[@mode]"));
+    }
+
+    /**
+     * Tests adding multiple values to an attribute.
+     */
+    public void testAddMultipleAttributeValues()
+    {
+        final String attrKey = "tables.table(0)[@mode]";
+        source.addProperty(attrKey, "system");
+        source.addProperty(attrKey, "security");
+        List<?> values = (List<?>) source.getProperty(attrKey);
+        assertEquals("Wrong number of values", 2, values.size());
+        assertEquals("Wrong value 1", "system", values.get(0));
+        assertEquals("Wrong value 2", "security", values.get(1));
+    }
+
+    /**
+     * Tests overriding an attribute with multiple values.
+     */
+    public void testOverrideMultipleAttributeValues()
+    {
+        final String attrKey = "tables.table(0)[@mode]";
+        testAddMultipleAttributeValues(); // set attribute values
+        source.setProperty(attrKey, "NewValue");
+        assertEquals("Wrong changed value", "NewValue", source
+                .getProperty(attrKey));
+    }
+
+    /**
+     * Tests find() for a null key. This should return the root node.
+     */
+    public void testFindNull()
+    {
+        NodeList<ConfigurationNode> list = source.find(null);
+        assertEquals("Wrong number of elements", 1, list.size());
+        assertEquals("Wrong result for null key", source.getRootNode(), list
+                .getNode(0));
+    }
+
+    /**
+     * Tests clearing the whole source.
+     */
+    public void testClear()
+    {
+        source.clear();
+        assertTrue("Source not empty", source.isEmpty());
+    }
+
+    /**
+     * A specialized visitor that only counts the visited nodes.
+     */
+    private static class CountVisitor extends
+            NodeVisitorAdapter<ConfigurationNode>
+    {
+        int countBefore;
+
+        int countAfter;
+
+        @Override
+        public void visitBeforeChildren(ConfigurationNode node,
+                NodeHandler<ConfigurationNode> handler)
+        {
+            countBefore++;
+        }
+
+        @Override
+        public void visitAfterChildren(ConfigurationNode node,
+                NodeHandler<ConfigurationNode> handler)
+        {
+            countAfter++;
+        }
+
+        /**
+         * Tests whether the expected number of nodes was found.
+         *
+         * @param expectedCount the expected number
+         */
+        public void check(int expectedCount)
+        {
+            assertEquals("Wrong before count", expectedCount, countBefore);
+            assertEquals("Wrong after count", expectedCount, countAfter);
+        }
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestInMemoryConfigurationSource.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain