View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration.tree.xpath;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertSame;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.commons.configuration.tree.ConfigurationNode;
29  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
30  import org.apache.commons.configuration.tree.NodeAddData;
31  import org.apache.commons.jxpath.JXPathContext;
32  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
33  import org.apache.commons.jxpath.ri.model.NodePointerFactory;
34  import org.junit.Before;
35  import org.junit.Test;
36  
37  /**
38   * Test class for XPathExpressionEngine.
39   *
40   * @author <a
41   * href="http://commons.apache.org/configuration/team-list.html">Commons
42   * Configuration team</a>
43   * @version $Id: TestXPathExpressionEngine.java 1226111 2011-12-31 15:44:50Z oheger $
44   */
45  public class TestXPathExpressionEngine
46  {
47      /** Constant for the test root node. */
48      static final ConfigurationNode ROOT = new DefaultConfigurationNode(
49              "testRoot");
50  
51      /** Constant for the valid test key. */
52      static final String TEST_KEY = "TESTKEY";
53  
54      /** The expression engine to be tested. */
55      XPathExpressionEngine engine;
56  
57      @Before
58      public void setUp() throws Exception
59      {
60          engine = new MockJXPathContextExpressionEngine();
61      }
62  
63      /**
64       * Tests the query() method with a normal expression.
65       */
66      @Test
67      public void testQueryExpression()
68      {
69          List<ConfigurationNode> nodes = engine.query(ROOT, TEST_KEY);
70          assertEquals("Incorrect number of results", 1, nodes.size());
71          assertSame("Wrong result node", ROOT, nodes.get(0));
72          checkSelectCalls(1);
73      }
74  
75      /**
76       * Tests a query that has no results. This should return an empty list.
77       */
78      @Test
79      public void testQueryWithoutResult()
80      {
81          List<ConfigurationNode> nodes = engine.query(ROOT, "a non existing key");
82          assertTrue("Result list is not empty", nodes.isEmpty());
83          checkSelectCalls(1);
84      }
85  
86      /**
87       * Tests a query with an empty key. This should directly return the root
88       * node without invoking the JXPathContext.
89       */
90      @Test
91      public void testQueryWithEmptyKey()
92      {
93          checkEmptyKey("");
94      }
95  
96      /**
97       * Tests a query with a null key. Same as an empty key.
98       */
99      @Test
100     public void testQueryWithNullKey()
101     {
102         checkEmptyKey(null);
103     }
104 
105     /**
106      * Helper method for testing undefined keys.
107      *
108      * @param key the key
109      */
110     private void checkEmptyKey(String key)
111     {
112         List<ConfigurationNode> nodes = engine.query(ROOT, key);
113         assertEquals("Incorrect number of results", 1, nodes.size());
114         assertSame("Wrong result node", ROOT, nodes.get(0));
115         checkSelectCalls(0);
116     }
117 
118     /**
119      * Tests if the used JXPathContext is correctly initialized.
120      */
121     @Test
122     public void testCreateContext()
123     {
124         JXPathContext ctx = new XPathExpressionEngine().createContext(ROOT,
125                 TEST_KEY);
126         assertNotNull("Context is null", ctx);
127         assertTrue("Lenient mode is not set", ctx.isLenient());
128         assertSame("Incorrect context bean set", ROOT, ctx.getContextBean());
129 
130         NodePointerFactory[] factories = JXPathContextReferenceImpl
131                 .getNodePointerFactories();
132         boolean found = false;
133         for (int i = 0; i < factories.length; i++)
134         {
135             if (factories[i] instanceof ConfigurationNodePointerFactory)
136             {
137                 found = true;
138             }
139         }
140         assertTrue("No configuration pointer factory found", found);
141     }
142 
143     /**
144      * Tests a normal call of nodeKey().
145      */
146     @Test
147     public void testNodeKeyNormal()
148     {
149         assertEquals("Wrong node key", "parent/child", engine.nodeKey(
150                 new DefaultConfigurationNode("child"), "parent"));
151     }
152 
153     /**
154      * Tests nodeKey() for an attribute node.
155      */
156     @Test
157     public void testNodeKeyAttribute()
158     {
159         ConfigurationNode node = new DefaultConfigurationNode("attr");
160         node.setAttribute(true);
161         assertEquals("Wrong attribute key", "node/@attr", engine.nodeKey(node,
162                 "node"));
163     }
164 
165     /**
166      * Tests nodeKey() for the root node.
167      */
168     @Test
169     public void testNodeKeyForRootNode()
170     {
171         assertEquals("Wrong key for root node", "", engine.nodeKey(ROOT, null));
172         assertEquals("Null name not detected", "test", engine.nodeKey(
173                 new DefaultConfigurationNode(), "test"));
174     }
175 
176     /**
177      * Tests node key() for direct children of the root node.
178      */
179     @Test
180     public void testNodeKeyForRootChild()
181     {
182         ConfigurationNode node = new DefaultConfigurationNode("child");
183         assertEquals("Wrong key for root child node", "child", engine.nodeKey(
184                 node, ""));
185         node.setAttribute(true);
186         assertEquals("Wrong key for root attribute", "@child", engine.nodeKey(
187                 node, ""));
188     }
189 
190     /**
191      * Tests adding a single child node.
192      */
193     @Test
194     public void testPrepareAddNode()
195     {
196         NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "  newNode");
197         checkAddPath(data, new String[]
198         { "newNode" }, false);
199         checkSelectCalls(1);
200     }
201 
202     /**
203      * Tests adding a new attribute node.
204      */
205     @Test
206     public void testPrepareAddAttribute()
207     {
208         NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "\t@newAttr");
209         checkAddPath(data, new String[]
210         { "newAttr" }, true);
211         checkSelectCalls(1);
212     }
213 
214     /**
215      * Tests adding a complete path.
216      */
217     @Test
218     public void testPrepareAddPath()
219     {
220         NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
221                 + " \t a/full/path/node");
222         checkAddPath(data, new String[]
223         { "a", "full", "path", "node" }, false);
224         checkSelectCalls(1);
225     }
226 
227     /**
228      * Tests adding a complete path whose final node is an attribute.
229      */
230     @Test
231     public void testPrepareAddAttributePath()
232     {
233         NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
234                 + " a/full/path@attr");
235         checkAddPath(data, new String[]
236         { "a", "full", "path", "attr" }, true);
237         checkSelectCalls(1);
238     }
239 
240     /**
241      * Tests adding a new node to the root.
242      */
243     @Test
244     public void testPrepareAddRootChild()
245     {
246         NodeAddData data = engine.prepareAdd(ROOT, " newNode");
247         checkAddPath(data, new String[]
248         { "newNode" }, false);
249         checkSelectCalls(0);
250     }
251 
252     /**
253      * Tests adding a new attribute to the root.
254      */
255     @Test
256     public void testPrepareAddRootAttribute()
257     {
258         NodeAddData data = engine.prepareAdd(ROOT, " @attr");
259         checkAddPath(data, new String[]
260         { "attr" }, true);
261         checkSelectCalls(0);
262     }
263 
264     /**
265      * Tests an add operation with a query that does not return a single node.
266      */
267     @Test(expected = IllegalArgumentException.class)
268     public void testPrepareAddInvalidParent()
269     {
270         engine.prepareAdd(ROOT, "invalidKey newNode");
271     }
272 
273     /**
274      * Tests an add operation with an empty path for the new node.
275      */
276     @Test(expected = IllegalArgumentException.class)
277     public void testPrepareAddEmptyPath()
278     {
279         engine.prepareAdd(ROOT, TEST_KEY + " ");
280     }
281 
282     /**
283      * Tests an add operation where the key is null.
284      */
285     @Test(expected = IllegalArgumentException.class)
286     public void testPrepareAddNullKey()
287     {
288         engine.prepareAdd(ROOT, null);
289     }
290 
291     /**
292      * Tests an add operation where the key is null.
293      */
294     @Test(expected = IllegalArgumentException.class)
295     public void testPrepareAddEmptyKey()
296     {
297         engine.prepareAdd(ROOT, "");
298     }
299 
300     /**
301      * Tests an add operation with an invalid path.
302      */
303     @Test(expected = IllegalArgumentException.class)
304     public void testPrepareAddInvalidPath()
305     {
306         engine.prepareAdd(ROOT, TEST_KEY + " an/invalid//path");
307     }
308 
309     /**
310      * Tests an add operation with an invalid path: the path contains an
311      * attribute in the middle part.
312      */
313     @Test(expected = IllegalArgumentException.class)
314     public void testPrepareAddInvalidAttributePath()
315     {
316         engine.prepareAdd(ROOT, TEST_KEY + " a/path/with@an/attribute");
317     }
318 
319     /**
320      * Tests an add operation with an invalid path: the path contains an
321      * attribute after a slash.
322      */
323     @Test(expected = IllegalArgumentException.class)
324     public void testPrepareAddInvalidAttributePath2()
325     {
326         engine.prepareAdd(ROOT, TEST_KEY + " a/path/with/@attribute");
327     }
328 
329     /**
330      * Tests an add operation with an invalid path that starts with a slash.
331      */
332     @Test(expected = IllegalArgumentException.class)
333     public void testPrepareAddInvalidPathWithSlash()
334     {
335         engine.prepareAdd(ROOT, TEST_KEY + " /a/path/node");
336     }
337 
338     /**
339      * Tests an add operation with an invalid path that contains multiple
340      * attribute components.
341      */
342     @Test(expected = IllegalArgumentException.class)
343     public void testPrepareAddInvalidPathMultipleAttributes()
344     {
345         engine.prepareAdd(ROOT, TEST_KEY + " an@attribute@path");
346     }
347 
348     /**
349      * Helper method for testing the path nodes in the given add data object.
350      *
351      * @param data the data object to check
352      * @param expected an array with the expected path elements
353      * @param attr a flag if the new node is an attribute
354      */
355     private void checkAddPath(NodeAddData data, String[] expected, boolean attr)
356     {
357         assertSame("Wrong parent node", ROOT, data.getParent());
358         List<String> path = data.getPathNodes();
359         assertEquals("Incorrect number of path nodes", expected.length - 1,
360                 path.size());
361         Iterator<String> it = path.iterator();
362         for (int idx = 0; idx < expected.length - 1; idx++)
363         {
364             assertEquals("Wrong node at position " + idx, expected[idx], it
365                     .next());
366         }
367         assertEquals("Wrong name of new node", expected[expected.length - 1],
368                 data.getNewNodeName());
369         assertEquals("Incorrect attribute flag", attr, data.isAttribute());
370     }
371 
372     /**
373      * Checks if the JXPath context's selectNodes() method was called as often
374      * as expected.
375      *
376      * @param expected the number of expected calls
377      */
378     protected void checkSelectCalls(int expected)
379     {
380         MockJXPathContext ctx = ((MockJXPathContextExpressionEngine) engine).getContext();
381         int calls = (ctx == null) ? 0 : ctx.selectInvocations;
382         assertEquals("Incorrect number of select calls", expected, calls);
383     }
384 
385     /**
386      * A mock implementation of the JXPathContext class. This implementation
387      * will overwrite the <code>selectNodes()</code> method that is used by
388      * <code>XPathExpressionEngine</code> to count the invocations of this
389      * method.
390      */
391     static class MockJXPathContext extends JXPathContextReferenceImpl
392     {
393         int selectInvocations;
394 
395         public MockJXPathContext(Object bean)
396         {
397             super(null, bean);
398         }
399 
400         /**
401          * Dummy implementation of this method. If the passed in string is the
402          * test key, the root node will be returned in the list. Otherwise the
403          * return value is <b>null</b>.
404          */
405         @Override
406         public List<?> selectNodes(String xpath)
407         {
408             selectInvocations++;
409             if (TEST_KEY.equals(xpath))
410             {
411                 List<ConfigurationNode> result = new ArrayList<ConfigurationNode>(1);
412                 result.add(ROOT);
413                 return result;
414             }
415             else
416             {
417                 return null;
418             }
419         }
420     }
421 
422     /**
423      * A special implementation of XPathExpressionEngine that overrides
424      * createContext() to return a mock context object.
425      */
426     static class MockJXPathContextExpressionEngine extends
427             XPathExpressionEngine
428     {
429         /** Stores the context instance. */
430         private MockJXPathContext context;
431 
432         @Override
433         protected JXPathContext createContext(ConfigurationNode root, String key)
434         {
435             context = new MockJXPathContext(root);
436             return context;
437         }
438 
439         /**
440          * Returns the context created by the last newContext() call.
441          *
442          * @return the current context
443          */
444         public MockJXPathContext getContext()
445         {
446             return context;
447         }
448     }
449 }