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