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 2008/03/15 21:11:34 UTC

svn commit: r637458 - in /commons/proper/configuration/branches/configuration2_experimental/src: main/java/org/apache/commons/configuration2/expr/xpath/ test/java/org/apache/commons/configuration2/expr/xpath/

Author: oheger
Date: Sat Mar 15 13:11:31 2008
New Revision: 637458

URL: http://svn.apache.org/viewvc?rev=637458&view=rev
Log:
Ported XPathExpressionEngine to support hierarchical configurations based on the NodeHandler approach

Added:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/XPathExpressionEngine.java
      - copied, changed from r635335, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/xpath/XPathExpressionEngine.java
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/package.html
      - copied unchanged from r635335, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/xpath/package.html
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/expr/xpath/TestXPathExpressionEngine.java
      - copied, changed from r636104, commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/xpath/TestXPathExpressionEngine.java

Copied: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/XPathExpressionEngine.java (from r635335, commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/xpath/XPathExpressionEngine.java)
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/XPathExpressionEngine.java?p2=commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/XPathExpressionEngine.java&p1=commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/xpath/XPathExpressionEngine.java&r1=635335&r2=637458&rev=637458&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/xpath/XPathExpressionEngine.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/expr/xpath/XPathExpressionEngine.java Sat Mar 15 13:11:31 2008
@@ -15,16 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.commons.configuration2.tree.xpath;
+package org.apache.commons.configuration2.expr.xpath;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.StringTokenizer;
 
-import org.apache.commons.configuration2.tree.ConfigurationNode;
-import org.apache.commons.configuration2.tree.ExpressionEngine;
-import org.apache.commons.configuration2.tree.NodeAddData;
+import org.apache.commons.configuration2.expr.ExpressionEngine;
+import org.apache.commons.configuration2.expr.NodeAddData;
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.expr.NodeList;
 import org.apache.commons.jxpath.JXPathContext;
 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
 import org.apache.commons.lang.StringUtils;
@@ -37,7 +36,7 @@
  * <p>
  * This class makes use of <a href="http://commons.apache.org/jxpath/">
  * Commons JXPath</a> for handling XPath expressions and mapping them to the
- * nodes of a hierarchical configuration. This makes the rich and powerfull
+ * nodes of a hierarchical configuration. This makes the rich and powerful
  * XPATH syntax available for accessing properties from a configuration object.
  * </p>
  * <p>
@@ -98,7 +97,7 @@
  * <code>name</code> will be added.
  * </p>
  *
- * @since 1.3
+ * @since 2.0
  * @author Oliver Heger
  * @version $Id$
  */
@@ -119,52 +118,69 @@
      *
      * @param root the configuration root node
      * @param key the query to be executed
+     * @param handler the node handler
      * @return a list with the nodes that are selected by the query
      */
-    public List<ConfigurationNode> query(ConfigurationNode root, String key)
+    @SuppressWarnings("unchecked")
+    public <T> NodeList<T> query(T root, String key, NodeHandler<T> handler)
     {
+        NodeList<T> result = new NodeList<T>();
+
         if (StringUtils.isEmpty(key))
         {
-            List<ConfigurationNode> result = new ArrayList<ConfigurationNode>(1);
-            result.add(root);
-            return result;
+            result.addNode(root);
         }
+
         else
         {
-            JXPathContext context = createContext(root, key);
-            List<ConfigurationNode> result = context.selectNodes(key);
-            if (result != null)
-            {
-                return result;
-            }
-            else
+            JXPathContext context = createContext(root, key, handler);
+            List<?> nodes = context.selectNodes(key);
+            if (nodes != null)
             {
-                return Collections.emptyList();
+                for (Object o : nodes)
+                {
+                    if (o instanceof ConfigurationAttributePointer.AttributeNodeProxy)
+                    {
+                        ConfigurationAttributePointer<T>.AttributeNodeProxy anp =
+                            (ConfigurationAttributePointer.AttributeNodeProxy) o;
+                        result.addAttribute(anp.getParentNode(), anp
+                                .getAttributeName());
+                    }
+                    else
+                    {
+                        result.addNode((T) o);
+                    }
+                }
             }
         }
+
+        return result;
     }
 
     /**
-     * Returns a (canonic) key for the given node based on the parent's key.
+     * Returns a (canonical) key for the given node based on the parent's key.
      * This implementation will create an XPATH expression that selects the
      * given node (under the assumption that the passed in parent key is valid).
      * As the <code>nodeKey()</code> implementation of
-     * <code>{@link org.apache.commons.configuration2.tree.DefaultExpressionEngine DefaultExpressionEngine}</code>
+     * <code>DefaultExpressionEngine</code>
      * this method will not return indices for nodes. So all child nodes of a
-     * given parent whith the same name will have the same key.
+     * given parent with the same name will have the same key.
      *
      * @param node the node for which a key is to be constructed
      * @param parentKey the key of the parent node
+     * @param handler the node handler
      * @return the key for the given node
      */
-    public String nodeKey(ConfigurationNode node, String parentKey)
+    public <T> String nodeKey(T node, String parentKey, NodeHandler<T> handler)
     {
         if (parentKey == null)
         {
             // name of the root node
             return StringUtils.EMPTY;
         }
-        else if (node.getName() == null)
+
+        String nodeName = handler.nodeName(node);
+        if (nodeName == null)
         {
             // paranoia check for undefined node names
             return parentKey;
@@ -172,31 +188,50 @@
 
         else
         {
-            StringBuilder buf = new StringBuilder(parentKey.length() + node.getName().length() + PATH_DELIMITER.length());
+            StringBuilder buf = new StringBuilder(parentKey.length() + nodeName.length() + PATH_DELIMITER.length());
             if (parentKey.length() > 0)
             {
                 buf.append(parentKey);
                 buf.append(PATH_DELIMITER);
             }
-            if (node.isAttribute())
-            {
-                buf.append(ATTR_DELIMITER);
-            }
-            buf.append(node.getName());
+            buf.append(nodeName);
             return buf.toString();
         }
     }
 
     /**
+     * Returns a key for the specified attribute. This method works similar to
+     * <code>nodeKey()</code>, but deals with attributes.
+     *
+     * @param parentNode the parent node
+     * @param parentKey the key of the parent
+     * @param attrName the name of the attribute
+     * @param handler the node handler
+     * @return the key for this attribute
+     */
+    public <T> String attributeKey(T parentNode, String parentKey,
+            String attrName, NodeHandler<T> handler)
+    {
+        StringBuilder buf = new StringBuilder();
+        if (parentKey != null && parentKey.length() > 0)
+        {
+            buf.append(parentKey).append(PATH_DELIMITER);
+        }
+        buf.append(ATTR_DELIMITER).append(attrName);
+        return buf.toString();
+    }
+
+    /**
      * Prepares an add operation for a configuration property. The expected
      * format of the passed in key is explained in the class comment.
      *
      * @param root the configuration's root node
      * @param key the key describing the target of the add operation and the
      * path of the new node
+     * @param handler the node handler
      * @return a data object to be evaluated by the calling configuration object
      */
-    public NodeAddData prepareAdd(ConfigurationNode root, String key)
+    public <T> NodeAddData<T> prepareAdd(T root, String key, NodeHandler<T> handler)
     {
         if (key == null)
         {
@@ -214,14 +249,14 @@
             throw new IllegalArgumentException("prepareAdd: Passed in key must contain a whitespace!");
         }
 
-        List<ConfigurationNode> nodes = query(root, key.substring(0, index).trim());
+        NodeList<T> nodes = query(root, key.substring(0, index).trim(), handler);
         if (nodes.size() != 1)
         {
             throw new IllegalArgumentException("prepareAdd: key must select exactly one target node!");
         }
 
-        NodeAddData data = new NodeAddData();
-        data.setParent(nodes.get(0));
+        NodeAddData<T> data = new NodeAddData<T>();
+        data.setParent(nodes.getNode(0));
         initNodeAddData(data, key.substring(index).trim());
         return data;
     }
@@ -233,11 +268,14 @@
      *
      * @param root the configuration root node
      * @param key the key to be queried
+     * @param handler the node handler
      * @return the new context
      */
-    protected JXPathContext createContext(ConfigurationNode root, String key)
+    protected <T> JXPathContext createContext(T root, String key, NodeHandler<T> handler)
     {
-        JXPathContext context = JXPathContext.newContext(root);
+        JXPathContext context = JXPathContext
+                .newContext(ConfigurationNodePointerFactory.wrapNode(root,
+                        handler));
         context.setLenient(true);
         return context;
     }
@@ -245,12 +283,12 @@
     /**
      * Initializes most properties of a <code>NodeAddData</code> object. This
      * method is called by <code>prepareAdd()</code> after the parent node has
-     * been found. Its task is to interprete the passed in path of the new node.
+     * been found. Its task is to interpret the passed in path of the new node.
      *
      * @param data the data object to initialize
      * @param path the path of the new node
      */
-    protected void initNodeAddData(NodeAddData data, String path)
+    protected <T> void initNodeAddData(NodeAddData<T> data, String path)
     {
         String lastComponent = null;
         boolean attr = false;

Copied: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/expr/xpath/TestXPathExpressionEngine.java (from r636104, commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/xpath/TestXPathExpressionEngine.java)
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/expr/xpath/TestXPathExpressionEngine.java?p2=commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/expr/xpath/TestXPathExpressionEngine.java&p1=commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/xpath/TestXPathExpressionEngine.java&r1=636104&r2=637458&rev=637458&view=diff
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/xpath/TestXPathExpressionEngine.java (original)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/expr/xpath/TestXPathExpressionEngine.java Sat Mar 15 13:11:31 2008
@@ -14,17 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.configuration2.tree.xpath;
+package org.apache.commons.configuration2.expr.xpath;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.commons.configuration2.expr.ConfigurationNodeHandler;
+import org.apache.commons.configuration2.expr.NodeAddData;
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.expr.NodeList;
 import org.apache.commons.configuration2.tree.ConfigurationNode;
 import org.apache.commons.configuration2.tree.DefaultConfigurationNode;
-import org.apache.commons.configuration2.tree.NodeAddData;
-import org.apache.commons.configuration2.tree.xpath.ConfigurationNodePointerFactory;
-import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
 import org.apache.commons.jxpath.JXPathContext;
 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
 import org.apache.commons.jxpath.ri.model.NodePointerFactory;
@@ -39,19 +40,24 @@
  */
 public class TestXPathExpressionEngine extends TestCase
 {
-    /** Constant for the test root node. */
-    static final ConfigurationNode ROOT = new DefaultConfigurationNode(
-            "testRoot");
-
     /** Constant for the valid test key. */
-    static final String TEST_KEY = "TESTKEY";
+    private static final String TEST_KEY = "TESTKEY";
+
+    /** The test root node. */
+    private ConfigurationNode root;
+
+    /** The node handler.*/
+    private ConfigurationNodeHandler handler;
 
     /** The expression engine to be tested. */
-    XPathExpressionEngine engine;
+    private MockJXPathContextExpressionEngine engine;
 
+    @Override
     protected void setUp() throws Exception
     {
         super.setUp();
+        root = new DefaultConfigurationNode("testRoot");
+        handler = new ConfigurationNodeHandler();
         engine = new MockJXPathContextExpressionEngine();
     }
 
@@ -60,19 +66,40 @@
      */
     public void testQueryExpression()
     {
-        List nodes = engine.query(ROOT, TEST_KEY);
+        NodeList<ConfigurationNode> nodes = engine.query(root, TEST_KEY, handler);
         assertEquals("Incorrect number of results", 1, nodes.size());
-        assertSame("Wrong result node", ROOT, nodes.get(0));
+        assertSame("Wrong result node", root, nodes.getNode(0));
         checkSelectCalls(1);
     }
 
     /**
+     * Tests a query that returns attribute nodes.
+     */
+    public void testQueryAttributes()
+    {
+        final int attrCount = 5;
+        for(int i = 0; i < attrCount; i++)
+        {
+            root.addAttribute(new DefaultConfigurationNode("attr" + i, "value" + i));
+        }
+        engine.useMockContext = false;
+        NodeList<ConfigurationNode> nodes = engine.query(root, "/@*", handler);
+        assertEquals("Wrong number of attribute results", attrCount, nodes.size());
+        for(int i = 0; i < attrCount; i++)
+        {
+            assertTrue("No attribute node", nodes.isAttribute(i));
+            assertEquals("Wrong attribute name", "attr" + i, nodes.getName(i, handler));
+            assertEquals("Wrong parent node", root, nodes.getAttributeParent(i));
+        }
+    }
+
+    /**
      * Tests a query that has no results. This should return an empty list.
      */
     public void testQueryWithoutResult()
     {
-        List nodes = engine.query(ROOT, "a non existing key");
-        assertTrue("Result list is not empty", nodes.isEmpty());
+        NodeList<ConfigurationNode> nodes = engine.query(root, "a non existing key", handler);
+        assertTrue("Result list is not empty", nodes.size() == 0);
         checkSelectCalls(1);
     }
 
@@ -100,9 +127,9 @@
      */
     private void checkEmptyKey(String key)
     {
-        List nodes = engine.query(ROOT, key);
+        NodeList<ConfigurationNode> nodes = engine.query(root, key, handler);
         assertEquals("Incorrect number of results", 1, nodes.size());
-        assertSame("Wrong result node", ROOT, nodes.get(0));
+        assertSame("Wrong result node", root, nodes.getNode(0));
         checkSelectCalls(0);
     }
 
@@ -111,11 +138,10 @@
      */
     public void testCreateContext()
     {
-        JXPathContext ctx = new XPathExpressionEngine().createContext(ROOT,
-                TEST_KEY);
+        JXPathContext ctx = new XPathExpressionEngine().createContext(root,
+                TEST_KEY, handler);
         assertNotNull("Context is null", ctx);
         assertTrue("Lenient mode is not set", ctx.isLenient());
-        assertSame("Incorrect context bean set", ROOT, ctx.getContextBean());
 
         NodePointerFactory[] factories = JXPathContextReferenceImpl
                 .getNodePointerFactories();
@@ -136,18 +162,19 @@
     public void testNodeKeyNormal()
     {
         assertEquals("Wrong node key", "parent/child", engine.nodeKey(
-                new DefaultConfigurationNode("child"), "parent"));
+                new DefaultConfigurationNode("child"), "parent", handler));
     }
 
     /**
-     * Tests nodeKey() for an attribute node.
+     * Tests querying the key of an attribute node.
      */
-    public void testNodeKeyAttribute()
+    public void testAttributeKey()
     {
+        ConfigurationNode parent = new DefaultConfigurationNode("node");
         ConfigurationNode node = new DefaultConfigurationNode("attr");
-        node.setAttribute(true);
-        assertEquals("Wrong attribute key", "node/@attr", engine.nodeKey(node,
-                "node"));
+        parent.addAttribute(node);
+        assertEquals("Wrong attribute key", "node/@attr", engine.attributeKey(
+                parent, parent.getName(), node.getName(), handler));
     }
 
     /**
@@ -155,22 +182,51 @@
      */
     public void testNodeKeyForRootNode()
     {
-        assertEquals("Wrong key for root node", "", engine.nodeKey(ROOT, null));
+        assertEquals("Wrong key for root node", "", engine.nodeKey(root, null, handler));
         assertEquals("Null name not detected", "test", engine.nodeKey(
-                new DefaultConfigurationNode(), "test"));
+                new DefaultConfigurationNode(), "test", handler));
     }
 
     /**
-     * Tests node key() for direct children of the root node.
+     * Tests nodeKey() for direct children of the root node.
      */
     public void testNodeKeyForRootChild()
     {
         ConfigurationNode node = new DefaultConfigurationNode("child");
         assertEquals("Wrong key for root child node", "child", engine.nodeKey(
-                node, ""));
-        node.setAttribute(true);
-        assertEquals("Wrong key for root attribute", "@child", engine.nodeKey(
-                node, ""));
+                node, "", handler));
+    }
+
+    /**
+     * Tests querying the key of an attribute that belongs to the root node when
+     * the empty string is passed as parent name.
+     */
+    public void testAttributeKeyForRootEmpty()
+    {
+        checkAttributeKeyForRoot("");
+    }
+
+    /**
+     * Tests querying the key of an attribute that belongs to the root node when
+     * null is passed as parent name.
+     */
+    public void testAttributeKeyForRootNull()
+    {
+        checkAttributeKeyForRoot(null);
+    }
+
+    /**
+     * Helper method for checking the keys of attributes that belong to the root
+     * node.
+     * @param rootName the key of the root node (null or empty)
+     */
+    private void checkAttributeKeyForRoot(String rootName)
+    {
+        ConfigurationNode parent = new DefaultConfigurationNode();
+        ConfigurationNode attr = new DefaultConfigurationNode("attr");
+        parent.addAttribute(attr);
+        assertEquals("Wrong key of root attribute", "@attr", engine
+                .attributeKey(parent, rootName, attr.getName(), handler));
     }
 
     /**
@@ -178,9 +234,8 @@
      */
     public void testPrepareAddNode()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "  newNode");
-        checkAddPath(data, new String[]
-        { "newNode" }, false);
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, TEST_KEY + "  newNode", handler);
+        checkAddPath(data, new String[] { "newNode" }, false);
         checkSelectCalls(1);
     }
 
@@ -189,9 +244,8 @@
      */
     public void testPrepareAddAttribute()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "\t@newAttr");
-        checkAddPath(data, new String[]
-        { "newAttr" }, true);
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, TEST_KEY + "\t@newAttr", handler);
+        checkAddPath(data, new String[] { "newAttr" }, true);
         checkSelectCalls(1);
     }
 
@@ -200,8 +254,8 @@
      */
     public void testPrepareAddPath()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
-                + " \t a/full/path/node");
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, TEST_KEY
+                + " \t a/full/path/node", handler);
         checkAddPath(data, new String[]
         { "a", "full", "path", "node" }, false);
         checkSelectCalls(1);
@@ -212,8 +266,8 @@
      */
     public void testPrepareAddAttributePath()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
-                + " a/full/path@attr");
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, TEST_KEY
+                + " a/full/path@attr", handler);
         checkAddPath(data, new String[]
         { "a", "full", "path", "attr" }, true);
         checkSelectCalls(1);
@@ -224,9 +278,8 @@
      */
     public void testPrepareAddRootChild()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, " newNode");
-        checkAddPath(data, new String[]
-        { "newNode" }, false);
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, " newNode", handler);
+        checkAddPath(data, new String[] { "newNode" }, false);
         checkSelectCalls(0);
     }
 
@@ -235,9 +288,8 @@
      */
     public void testPrepareAddRootAttribute()
     {
-        NodeAddData data = engine.prepareAdd(ROOT, " @attr");
-        checkAddPath(data, new String[]
-        { "attr" }, true);
+        NodeAddData<ConfigurationNode> data = engine.prepareAdd(root, " @attr", handler);
+        checkAddPath(data, new String[] { "attr" }, true);
         checkSelectCalls(0);
     }
 
@@ -248,7 +300,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, "invalidKey newNode");
+            engine.prepareAdd(root, "invalidKey newNode", handler);
             fail("Could add to invalid parent!");
         }
         catch (IllegalArgumentException iex)
@@ -259,13 +311,13 @@
 
     /**
      * Tests an add operation where the passed in key has an invalid format: it
-     * does not contain a whitspace. This will cause an error.
+     * does not contain a whitespace. This will cause an error.
      */
     public void testPrepareAddInvalidFormat()
     {
         try
         {
-            engine.prepareAdd(ROOT, "anInvalidKey");
+            engine.prepareAdd(root, "anInvalidKey", handler);
             fail("Could add an invalid key!");
         }
         catch (IllegalArgumentException iex)
@@ -281,7 +333,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " ");
+            engine.prepareAdd(root, TEST_KEY + " ", handler);
             fail("Could add empty path!");
         }
         catch (IllegalArgumentException iex)
@@ -297,7 +349,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, null);
+            engine.prepareAdd(root, null, handler);
             fail("Could add null path!");
         }
         catch (IllegalArgumentException iex)
@@ -307,13 +359,13 @@
     }
 
     /**
-     * Tests an add operation where the key is null.
+     * Tests an add operation where the key is empty.
      */
     public void testPrepareAddEmptyKey()
     {
         try
         {
-            engine.prepareAdd(ROOT, "");
+            engine.prepareAdd(root, "", handler);
             fail("Could add empty path!");
         }
         catch (IllegalArgumentException iex)
@@ -329,7 +381,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " an/invalid//path");
+            engine.prepareAdd(root, TEST_KEY + " an/invalid//path", handler);
             fail("Could add invalid path!");
         }
         catch (IllegalArgumentException iex)
@@ -346,7 +398,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " a/path/with@an/attribute");
+            engine.prepareAdd(root, TEST_KEY + " a/path/with@an/attribute", handler);
             fail("Could add invalid attribute path!");
         }
         catch (IllegalArgumentException iex)
@@ -363,7 +415,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " a/path/with/@attribute");
+            engine.prepareAdd(root, TEST_KEY + " a/path/with/@attribute", handler);
             fail("Could add invalid attribute path!");
         }
         catch (IllegalArgumentException iex)
@@ -379,7 +431,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " /a/path/node");
+            engine.prepareAdd(root, TEST_KEY + " /a/path/node", handler);
             fail("Could add path starting with a slash!");
         }
         catch (IllegalArgumentException iex)
@@ -396,7 +448,7 @@
     {
         try
         {
-            engine.prepareAdd(ROOT, TEST_KEY + " an@attribute@path");
+            engine.prepareAdd(root, TEST_KEY + " an@attribute@path", handler);
             fail("Could add path with multiple attributes!");
         }
         catch (IllegalArgumentException iex)
@@ -406,19 +458,38 @@
     }
 
     /**
+     * Tries to add a node to an attribute. This should cause an exception.
+     */
+    public void testPrepareAddToAttribute()
+    {
+        ConfigurationNode attr = new DefaultConfigurationNode("attr");
+        root.addAttribute(attr);
+        engine.useMockContext = false;
+        try
+        {
+            engine.prepareAdd(root, "/@attr newNode", handler);
+            fail("Could add a node to an attribute!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
      * Helper method for testing the path nodes in the given add data object.
      *
      * @param data the data object to check
      * @param expected an array with the expected path elements
      * @param attr a flag if the new node is an attribute
      */
-    private void checkAddPath(NodeAddData data, String[] expected, boolean attr)
+    private void checkAddPath(NodeAddData<ConfigurationNode> data, String[] expected, boolean attr)
     {
-        assertSame("Wrong parent node", ROOT, data.getParent());
-        List path = data.getPathNodes();
+        assertSame("Wrong parent node", root, data.getParent());
+        List<String> path = data.getPathNodes();
         assertEquals("Incorrect number of path nodes", expected.length - 1,
                 path.size());
-        Iterator it = path.iterator();
+        Iterator<String> it = path.iterator();
         for (int idx = 0; idx < expected.length - 1; idx++)
         {
             assertEquals("Wrong node at position " + idx, expected[idx], it
@@ -448,7 +519,7 @@
      * <code>XPathExpressionEngine</code> to count the invocations of this
      * method.
      */
-    static class MockJXPathContext extends JXPathContextReferenceImpl
+    class MockJXPathContext extends JXPathContextReferenceImpl
     {
         int selectInvocations;
 
@@ -462,13 +533,14 @@
          * test key, the root node will be returned in the list. Otherwise the
          * return value is <b>null</b>.
          */
-        public List selectNodes(String xpath)
+        @Override
+        public List<Object> selectNodes(String xpath)
         {
             selectInvocations++;
             if (TEST_KEY.equals(xpath))
             {
-                List result = new ArrayList(1);
-                result.add(ROOT);
+                List<Object> result = new ArrayList<Object>(1);
+                result.add(root);
                 return result;
             }
             else
@@ -480,18 +552,29 @@
 
     /**
      * A special implementation of XPathExpressionEngine that overrides
-     * createContext() to return a mock context object.
+     * createContext() to optionally return a mock context object.
      */
-    static class MockJXPathContextExpressionEngine extends
+    class MockJXPathContextExpressionEngine extends
             XPathExpressionEngine
     {
         /** Stores the context instance. */
         private MockJXPathContext context;
 
-        protected JXPathContext createContext(ConfigurationNode root, String key)
+        /** A flag whether a mock context is to be created.*/
+        boolean useMockContext = true;
+
+        @Override
+        protected <T> JXPathContext createContext(T root, String key, NodeHandler<T> handler)
         {
-            context = new MockJXPathContext(root);
-            return context;
+            if(useMockContext)
+            {
+                context = new MockJXPathContext(root);
+                return context;
+            }
+            else
+            {
+                return super.createContext(root, key, handler);
+            }
         }
 
         /**