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