001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.configuration.tree.xpath;
018:
019: import java.util.ArrayList;
020: import java.util.Iterator;
021: import java.util.List;
022:
023: import org.apache.commons.configuration.tree.ConfigurationNode;
024: import org.apache.commons.configuration.tree.DefaultConfigurationNode;
025: import org.apache.commons.configuration.tree.NodeAddData;
026: import org.apache.commons.jxpath.JXPathContext;
027: import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
028: import org.apache.commons.jxpath.ri.model.NodePointerFactory;
029:
030: import junit.framework.TestCase;
031:
032: /**
033: * Test class for XPathExpressionEngine.
034: *
035: * @author Oliver Heger
036: * @version $Id: TestXPathExpressionEngine.java 502705 2007-02-02 19:55:37Z oheger $
037: */
038: public class TestXPathExpressionEngine extends TestCase {
039: /** Constant for the test root node. */
040: static final ConfigurationNode ROOT = new DefaultConfigurationNode(
041: "testRoot");
042:
043: /** Constant for the valid test key. */
044: static final String TEST_KEY = "TESTKEY";
045:
046: /** The expression engine to be tested. */
047: XPathExpressionEngine engine;
048:
049: protected void setUp() throws Exception {
050: super .setUp();
051: engine = new MockJXPathContextExpressionEngine();
052: }
053:
054: /**
055: * Tests the query() method with a normal expression.
056: */
057: public void testQueryExpression() {
058: List nodes = engine.query(ROOT, TEST_KEY);
059: assertEquals("Incorrect number of results", 1, nodes.size());
060: assertSame("Wrong result node", ROOT, nodes.get(0));
061: checkSelectCalls(1);
062: }
063:
064: /**
065: * Tests a query that has no results. This should return an empty list.
066: */
067: public void testQueryWithoutResult() {
068: List nodes = engine.query(ROOT, "a non existing key");
069: assertTrue("Result list is not empty", nodes.isEmpty());
070: checkSelectCalls(1);
071: }
072:
073: /**
074: * Tests a query with an empty key. This should directly return the root
075: * node without invoking the JXPathContext.
076: */
077: public void testQueryWithEmptyKey() {
078: checkEmptyKey("");
079: }
080:
081: /**
082: * Tests a query with a null key. Same as an empty key.
083: */
084: public void testQueryWithNullKey() {
085: checkEmptyKey(null);
086: }
087:
088: /**
089: * Helper method for testing undefined keys.
090: *
091: * @param key the key
092: */
093: private void checkEmptyKey(String key) {
094: List nodes = engine.query(ROOT, key);
095: assertEquals("Incorrect number of results", 1, nodes.size());
096: assertSame("Wrong result node", ROOT, nodes.get(0));
097: checkSelectCalls(0);
098: }
099:
100: /**
101: * Tests if the used JXPathContext is correctly initialized.
102: */
103: public void testCreateContext() {
104: JXPathContext ctx = new XPathExpressionEngine().createContext(
105: ROOT, TEST_KEY);
106: assertNotNull("Context is null", ctx);
107: assertTrue("Lenient mode is not set", ctx.isLenient());
108: assertSame("Incorrect context bean set", ROOT, ctx
109: .getContextBean());
110:
111: NodePointerFactory[] factories = JXPathContextReferenceImpl
112: .getNodePointerFactories();
113: boolean found = false;
114: for (int i = 0; i < factories.length; i++) {
115: if (factories[i] instanceof ConfigurationNodePointerFactory) {
116: found = true;
117: }
118: }
119: assertTrue("No configuration pointer factory found", found);
120: }
121:
122: /**
123: * Tests a normal call of nodeKey().
124: */
125: public void testNodeKeyNormal() {
126: assertEquals("Wrong node key", "parent/child", engine.nodeKey(
127: new DefaultConfigurationNode("child"), "parent"));
128: }
129:
130: /**
131: * Tests nodeKey() for an attribute node.
132: */
133: public void testNodeKeyAttribute() {
134: ConfigurationNode node = new DefaultConfigurationNode("attr");
135: node.setAttribute(true);
136: assertEquals("Wrong attribute key", "node/@attr", engine
137: .nodeKey(node, "node"));
138: }
139:
140: /**
141: * Tests nodeKey() for the root node.
142: */
143: public void testNodeKeyForRootNode() {
144: assertEquals("Wrong key for root node", "", engine.nodeKey(
145: ROOT, null));
146: assertEquals("Null name not detected", "test", engine.nodeKey(
147: new DefaultConfigurationNode(), "test"));
148: }
149:
150: /**
151: * Tests node key() for direct children of the root node.
152: */
153: public void testNodeKeyForRootChild() {
154: ConfigurationNode node = new DefaultConfigurationNode("child");
155: assertEquals("Wrong key for root child node", "child", engine
156: .nodeKey(node, ""));
157: node.setAttribute(true);
158: assertEquals("Wrong key for root attribute", "@child", engine
159: .nodeKey(node, ""));
160: }
161:
162: /**
163: * Tests adding a single child node.
164: */
165: public void testPrepareAddNode() {
166: NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
167: + " newNode");
168: checkAddPath(data, new String[] { "newNode" }, false);
169: checkSelectCalls(1);
170: }
171:
172: /**
173: * Tests adding a new attribute node.
174: */
175: public void testPrepareAddAttribute() {
176: NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
177: + "\t@newAttr");
178: checkAddPath(data, new String[] { "newAttr" }, true);
179: checkSelectCalls(1);
180: }
181:
182: /**
183: * Tests adding a complete path.
184: */
185: public void testPrepareAddPath() {
186: NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
187: + " \t a/full/path/node");
188: checkAddPath(data,
189: new String[] { "a", "full", "path", "node" }, false);
190: checkSelectCalls(1);
191: }
192:
193: /**
194: * Tests adding a complete path whose final node is an attribute.
195: */
196: public void testPrepareAddAttributePath() {
197: NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
198: + " a/full/path@attr");
199: checkAddPath(data,
200: new String[] { "a", "full", "path", "attr" }, true);
201: checkSelectCalls(1);
202: }
203:
204: /**
205: * Tests adding a new node to the root.
206: */
207: public void testPrepareAddRootChild() {
208: NodeAddData data = engine.prepareAdd(ROOT, " newNode");
209: checkAddPath(data, new String[] { "newNode" }, false);
210: checkSelectCalls(0);
211: }
212:
213: /**
214: * Tests adding a new attribute to the root.
215: */
216: public void testPrepareAddRootAttribute() {
217: NodeAddData data = engine.prepareAdd(ROOT, " @attr");
218: checkAddPath(data, new String[] { "attr" }, true);
219: checkSelectCalls(0);
220: }
221:
222: /**
223: * Tests an add operation with a query that does not return a single node.
224: */
225: public void testPrepareAddInvalidParent() {
226: try {
227: engine.prepareAdd(ROOT, "invalidKey newNode");
228: fail("Could add to invalid parent!");
229: } catch (IllegalArgumentException iex) {
230: // ok
231: }
232: }
233:
234: /**
235: * Tests an add operation where the passed in key has an invalid format: it
236: * does not contain a whitspace. This will cause an error.
237: */
238: public void testPrepareAddInvalidFormat() {
239: try {
240: engine.prepareAdd(ROOT, "anInvalidKey");
241: fail("Could add an invalid key!");
242: } catch (IllegalArgumentException iex) {
243: // ok
244: }
245: }
246:
247: /**
248: * Tests an add operation with an empty path for the new node.
249: */
250: public void testPrepareAddEmptyPath() {
251: try {
252: engine.prepareAdd(ROOT, TEST_KEY + " ");
253: fail("Could add empty path!");
254: } catch (IllegalArgumentException iex) {
255: // ok
256: }
257: }
258:
259: /**
260: * Tests an add operation where the key is null.
261: */
262: public void testPrepareAddNullKey() {
263: try {
264: engine.prepareAdd(ROOT, null);
265: fail("Could add null path!");
266: } catch (IllegalArgumentException iex) {
267: // ok
268: }
269: }
270:
271: /**
272: * Tests an add operation where the key is null.
273: */
274: public void testPrepareAddEmptyKey() {
275: try {
276: engine.prepareAdd(ROOT, "");
277: fail("Could add empty path!");
278: } catch (IllegalArgumentException iex) {
279: // ok
280: }
281: }
282:
283: /**
284: * Tests an add operation with an invalid path.
285: */
286: public void testPrepareAddInvalidPath() {
287: try {
288: engine.prepareAdd(ROOT, TEST_KEY + " an/invalid//path");
289: fail("Could add invalid path!");
290: } catch (IllegalArgumentException iex) {
291: // ok
292: }
293: }
294:
295: /**
296: * Tests an add operation with an invalid path: the path contains an
297: * attribute in the middle part.
298: */
299: public void testPrepareAddInvalidAttributePath() {
300: try {
301: engine.prepareAdd(ROOT, TEST_KEY
302: + " a/path/with@an/attribute");
303: fail("Could add invalid attribute path!");
304: } catch (IllegalArgumentException iex) {
305: // ok
306: }
307: }
308:
309: /**
310: * Tests an add operation with an invalid path: the path contains an
311: * attribute after a slash.
312: */
313: public void testPrepareAddInvalidAttributePath2() {
314: try {
315: engine.prepareAdd(ROOT, TEST_KEY
316: + " a/path/with/@attribute");
317: fail("Could add invalid attribute path!");
318: } catch (IllegalArgumentException iex) {
319: // ok
320: }
321: }
322:
323: /**
324: * Tests an add operation with an invalid path that starts with a slash.
325: */
326: public void testPrepareAddInvalidPathWithSlash() {
327: try {
328: engine.prepareAdd(ROOT, TEST_KEY + " /a/path/node");
329: fail("Could add path starting with a slash!");
330: } catch (IllegalArgumentException iex) {
331: // ok
332: }
333: }
334:
335: /**
336: * Tests an add operation with an invalid path that contains multiple
337: * attribute components.
338: */
339: public void testPrepareAddInvalidPathMultipleAttributes() {
340: try {
341: engine.prepareAdd(ROOT, TEST_KEY + " an@attribute@path");
342: fail("Could add path with multiple attributes!");
343: } catch (IllegalArgumentException iex) {
344: // ok
345: }
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,
356: boolean attr) {
357: assertSame("Wrong parent node", ROOT, data.getParent());
358: List path = data.getPathNodes();
359: assertEquals("Incorrect number of path nodes",
360: expected.length - 1, path.size());
361: Iterator it = path.iterator();
362: for (int idx = 0; idx < expected.length - 1; idx++) {
363: assertEquals("Wrong node at position " + idx,
364: expected[idx], it.next());
365: }
366: assertEquals("Wrong name of new node",
367: expected[expected.length - 1], data.getNewNodeName());
368: assertEquals("Incorrect attribute flag", attr, data
369: .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: MockJXPathContext ctx = ((MockJXPathContextExpressionEngine) engine)
380: .getContext();
381: int calls = (ctx == null) ? 0 : ctx.selectInvocations;
382: assertEquals("Incorrect number of select calls", expected,
383: calls);
384: }
385:
386: /**
387: * A mock implementation of the JXPathContext class. This implementation
388: * will overwrite the <code>selectNodes()</code> method that is used by
389: * <code>XPathExpressionEngine</code> to count the invocations of this
390: * method.
391: */
392: static class MockJXPathContext extends JXPathContextReferenceImpl {
393: int selectInvocations;
394:
395: public MockJXPathContext(Object bean) {
396: super (null, bean);
397: }
398:
399: /**
400: * Dummy implementation of this method. If the passed in string is the
401: * test key, the root node will be returned in the list. Otherwise the
402: * return value is <b>null</b>.
403: */
404: public List selectNodes(String xpath) {
405: selectInvocations++;
406: if (TEST_KEY.equals(xpath)) {
407: List result = new ArrayList(1);
408: result.add(ROOT);
409: return result;
410: } else {
411: return null;
412: }
413: }
414: }
415:
416: /**
417: * A special implementation of XPathExpressionEngine that overrides
418: * createContext() to return a mock context object.
419: */
420: static class MockJXPathContextExpressionEngine extends
421: XPathExpressionEngine {
422: /** Stores the context instance. */
423: private MockJXPathContext context;
424:
425: protected JXPathContext createContext(ConfigurationNode root,
426: String key) {
427: context = new MockJXPathContext(root);
428: return context;
429: }
430:
431: /**
432: * Returns the context created by the last newContext() call.
433: *
434: * @return the current context
435: */
436: public MockJXPathContext getContext() {
437: return context;
438: }
439: }
440: }
|