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.configuration2.tree;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertSame;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.Arrays;
26  import java.util.List;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.junit.jupiter.api.BeforeAll;
30  import org.junit.jupiter.api.BeforeEach;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Test class for DefaultExpressionEngine.
35   */
36  public class TestDefaultExpressionEngine {
37      /** Stores the names of the test nodes representing tables. */
38      private static final String[] tables = {"users", "documents"};
39  
40      /** Stores the types of the test table nodes. */
41      private static final String[] tabTypes = {"system", "application"};
42  
43      /** Test data fields for the node hierarchy. */
44      private static final String[][] fields = {{"uid", "uname", "firstName", "lastName", "email"}, {"docid", "name", "creationDate", "authorID", "version"}};
45  
46      /** The root of a hierarchy with test nodes. */
47      private static ImmutableNode root;
48  
49      /** A node handler for the hierarchy of test nodes. */
50      private static NodeHandler<ImmutableNode> handler;
51  
52      /**
53       * Helper method for creating a field node with its children for the test node hierarchy.
54       *
55       * @param name the name of the field
56       * @return the field node
57       */
58      private static ImmutableNode createFieldNode(final String name) {
59          final ImmutableNode.Builder nodeFieldBuilder = new ImmutableNode.Builder(1);
60          nodeFieldBuilder.addChild(createNode("name", name));
61          return nodeFieldBuilder.name("field").create();
62      }
63  
64      /**
65       * Convenience method for creating a simple node with a name and a value.
66       *
67       * @param name the node name
68       * @param value the node value
69       * @return the node instance
70       */
71      private static ImmutableNode createNode(final String name, final Object value) {
72          return new ImmutableNode.Builder().name(name).value(value).create();
73      }
74  
75      @BeforeAll
76      public static void setUpBeforeClass() {
77          root = setUpNodes();
78          handler = new InMemoryNodeModel(root).getNodeHandler();
79      }
80  
81      /**
82       * Creates a node hierarchy for testing that consists of tables, their fields, and some additional data:
83       *
84       * <pre>
85       *  tables
86       *       table
87       *          name
88       *          fields
89       *              field
90       *                  name
91       *              field
92       *                  name
93       * </pre>
94       *
95       * @return the root of the test node hierarchy
96       */
97      private static ImmutableNode setUpNodes() {
98          final ImmutableNode.Builder nodeTablesBuilder = new ImmutableNode.Builder(tables.length);
99          nodeTablesBuilder.name("tables");
100         for (int i = 0; i < tables.length; i++) {
101             final ImmutableNode.Builder nodeTableBuilder = new ImmutableNode.Builder(2);
102             nodeTableBuilder.name("table");
103             nodeTableBuilder.addChild(new ImmutableNode.Builder().name("name").value(tables[i]).create());
104             nodeTableBuilder.addAttribute("type", tabTypes[i]);
105 
106             final ImmutableNode.Builder nodeFieldsBuilder = new ImmutableNode.Builder(fields[i].length);
107             for (int j = 0; j < fields[i].length; j++) {
108                 nodeFieldsBuilder.addChild(createFieldNode(fields[i][j]));
109             }
110             nodeTableBuilder.addChild(nodeFieldsBuilder.name("fields").create());
111             nodeTablesBuilder.addChild(nodeTableBuilder.create());
112         }
113 
114         final ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder();
115         rootBuilder.addChild(nodeTablesBuilder.create());
116         final ImmutableNode.Builder nodeConnBuilder = new ImmutableNode.Builder();
117         nodeConnBuilder.name("connection.settings");
118         nodeConnBuilder.addChild(createNode("usr.name", "scott"));
119         nodeConnBuilder.addChild(createNode("usr.pwd", "tiger"));
120         rootBuilder.addAttribute("test", "true");
121         rootBuilder.addChild(nodeConnBuilder.create());
122 
123         return rootBuilder.create();
124     }
125 
126     /** The object to be tested. */
127     private DefaultExpressionEngine engine;
128 
129     /**
130      * Helper method for checking whether an attribute key is correctly evaluated.
131      *
132      * @param key the attribute key
133      * @param attr the attribute name
134      * @param expValue the expected attribute value
135      */
136     private void checkAttributeValue(final String key, final String attr, final Object expValue) {
137         final List<QueryResult<ImmutableNode>> results = checkKey(key, attr, 1);
138         final QueryResult<ImmutableNode> result = results.get(0);
139         assertTrue(result.isAttributeResult());
140         assertEquals(expValue, result.getAttributeValue(handler), "Wrong attribute value for key " + key);
141     }
142 
143     /**
144      * Helper method for checking the evaluation of a key. Queries the expression engine and tests if the expected results
145      * are returned.
146      *
147      * @param key the key
148      * @param name the name of the nodes to be returned
149      * @param count the number of expected result nodes
150      * @return the list with the results of the query
151      */
152     private List<QueryResult<ImmutableNode>> checkKey(final String key, final String name, final int count) {
153         final List<QueryResult<ImmutableNode>> nodes = query(key, count);
154         for (final QueryResult<ImmutableNode> result : nodes) {
155             if (result.isAttributeResult()) {
156                 assertEquals(name, result.getAttributeName(), "Wrong attribute name for key " + key);
157             } else {
158                 assertEquals(name, result.getNode().getNodeName(), "Wrong result node for key " + key);
159             }
160         }
161         return nodes;
162     }
163 
164     /**
165      * Helper method for checking the value of a node specified by the given key. This method evaluates the key and checks
166      * whether the resulting node has the expected value.
167      *
168      * @param key the key
169      * @param name the expected name of the result node
170      * @param value the expected value of the result node
171      */
172     private void checkKeyValue(final String key, final String name, final String value) {
173         final List<QueryResult<ImmutableNode>> results = checkKey(key, name, 1);
174         final QueryResult<ImmutableNode> result = results.get(0);
175         assertFalse(result.isAttributeResult());
176         assertEquals(value, result.getNode().getValue(), "Wrong value for key " + key);
177     }
178 
179     /**
180      * Helper method for checking the path of an add operation.
181      *
182      * @param data the add data object
183      * @param expected the expected path nodes
184      */
185     private void checkNodePath(final NodeAddData<ImmutableNode> data, final String... expected) {
186         assertEquals(Arrays.asList(expected), data.getPathNodes());
187     }
188 
189     /**
190      * Helper method for testing a query for the root node.
191      *
192      * @param key the key to be used
193      */
194     private void checkQueryRootNode(final String key) {
195         final List<QueryResult<ImmutableNode>> results = checkKey(key, null, 1);
196         final QueryResult<ImmutableNode> result = results.get(0);
197         assertFalse(result.isAttributeResult());
198         assertSame(root, result.getNode());
199     }
200 
201     /**
202      * Helper method for fetching a specific node by its key.
203      *
204      * @param key the key
205      * @return the node with this key
206      */
207     private ImmutableNode fetchNode(final String key) {
208         final QueryResult<ImmutableNode> result = query(key, 1).get(0);
209         assertFalse(result.isAttributeResult());
210         return result.getNode();
211     }
212 
213     /**
214      * Helper method for querying the test engine for a specific key.
215      *
216      * @param key the key
217      * @param expCount the expected number of result nodes
218      * @return the collection of retrieved nodes
219      */
220     private List<QueryResult<ImmutableNode>> query(final String key, final int expCount) {
221         final List<QueryResult<ImmutableNode>> nodes = engine.query(root, key, handler);
222         assertEquals(expCount, nodes.size());
223         return nodes;
224     }
225 
226     @BeforeEach
227     public void setUp() throws Exception {
228         engine = DefaultExpressionEngine.INSTANCE;
229     }
230 
231     /**
232      * Configures the test expression engine to use a special matcher. This matcher ignores underscore characters in node
233      * names.
234      */
235     private void setUpAlternativeMatcher() {
236         final NodeMatcher<String> matcher = new NodeMatcher<String>() {
237             @Override
238             public <T> boolean matches(final T node, final NodeHandler<T> handler, final String criterion) {
239                 return handler.nodeName(node).equals(StringUtils.remove(criterion, '_'));
240             }
241         };
242         engine = new DefaultExpressionEngine(engine.getSymbols(), matcher);
243     }
244 
245     /**
246      * Configures the expression engine to use a different syntax.
247      */
248     private void setUpAlternativeSyntax() {
249         final DefaultExpressionEngineSymbols symbols = new DefaultExpressionEngineSymbols.Builder().setAttributeEnd(null).setAttributeStart("@")
250             .setPropertyDelimiter("/").setEscapedDelimiter(null).setIndexStart("[").setIndexEnd("]").create();
251         engine = new DefaultExpressionEngine(symbols);
252     }
253 
254     /**
255      * Tests obtaining keys for attribute nodes.
256      */
257     @Test
258     public void testAttributeKey() {
259         assertEquals("tables.table[@type]", engine.attributeKey("tables.table", "type"));
260     }
261 
262     /**
263      * Tests that a null parent key is ignored when constructing an attribute key.
264      */
265     @Test
266     public void testAttributeKeyNoParent() {
267         assertEquals("[@test]", engine.attributeKey(null, "test"));
268     }
269 
270     /**
271      * Tests whether an attribute key can be queried if the root node is involved.
272      */
273     @Test
274     public void testAttributeKeyRoot() {
275         assertEquals("[@test]", engine.attributeKey("", "test"));
276     }
277 
278     /**
279      * Tests whether a correct attribute key with alternative syntax is generated.
280      */
281     @Test
282     public void testAttributeKeyWithAlternativeSyntax() {
283         setUpAlternativeSyntax();
284         assertEquals("@test", engine.attributeKey("", "test"));
285     }
286 
287     /**
288      * Tests whether a canonical key can be queried if all child nodes have different names.
289      */
290     @Test
291     public void testCanonicalKeyNoDuplicates() {
292         final ImmutableNode node = fetchNode("tables.table(0).name");
293         assertEquals("table.name(0)", engine.canonicalKey(node, "table", handler));
294     }
295 
296     /**
297      * Tests whether the parent key can be undefined when querying a canonical key.
298      */
299     @Test
300     public void testCanonicalKeyNoParentKey() {
301         final ImmutableNode node = fetchNode("tables.table(0).fields.field(1).name");
302         assertEquals("name(0)", engine.canonicalKey(node, null, handler));
303     }
304 
305     /**
306      * Tests whether a canonical key for the parent node can be queried if no parent key was passed in.
307      */
308     @Test
309     public void testCanonicalKeyRootNoParentKey() {
310         assertEquals("", engine.canonicalKey(root, null, handler));
311     }
312 
313     /**
314      * Tests whether a parent key is evaluated when determining the canonical key of the root node.
315      */
316     @Test
317     public void testCanonicalKeyRootWithParentKey() {
318         assertEquals("parent", engine.canonicalKey(root, "parent", handler));
319     }
320 
321     /**
322      * Tests whether duplicates are correctly resolved when querying for canonical keys.
323      */
324     @Test
325     public void testCanonicalKeyWithDuplicates() {
326         final ImmutableNode tab1 = fetchNode("tables.table(0)");
327         final ImmutableNode tab2 = fetchNode("tables.table(1)");
328         assertEquals("tables.table(0)", engine.canonicalKey(tab1, "tables", handler));
329         assertEquals("tables.table(1)", engine.canonicalKey(tab2, "tables", handler));
330     }
331 
332     /**
333      * Tests whether the default instance is initialized with default symbols.
334      */
335     @Test
336     public void testDefaultSymbols() {
337         assertSame(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS, engine.getSymbols());
338     }
339 
340     /**
341      * Tries to create an instance without symbols.
342      */
343     @Test
344     public void testInitNoSymbols() {
345         assertThrows(IllegalArgumentException.class, () -> new DefaultExpressionEngine(null));
346     }
347 
348     /**
349      * Tests obtaining keys for nodes.
350      */
351     @Test
352     public void testNodeKey() {
353         final ImmutableNode node = root.getChildren().get(0);
354         assertEquals("tables", engine.nodeKey(node, "", handler));
355         assertEquals("test.tables", engine.nodeKey(node, "test", handler));
356         assertEquals("a.full.parent.key.tables", engine.nodeKey(node, "a.full.parent.key", handler));
357     }
358 
359     /**
360      * Tests obtaining node keys if a different syntax is set.
361      */
362     @Test
363     public void testNodeKeyWithAlternativeSyntax() {
364         setUpAlternativeSyntax();
365         assertEquals("tables/table", engine.nodeKey(root.getChildren().get(0).getChildren().get(0), "tables", handler));
366     }
367 
368     /**
369      * Tests obtaining node keys if a different syntax is set and the same string is used as property delimiter and
370      * attribute start marker.
371      */
372     @Test
373     public void testNodeKeyWithAlternativeSyntaxAttributePropertyDelimiter() {
374         setUpAlternativeSyntax();
375         final DefaultExpressionEngineSymbols symbols = new DefaultExpressionEngineSymbols.Builder(engine.getSymbols())
376             .setAttributeStart(engine.getSymbols().getPropertyDelimiter()).create();
377         engine = new DefaultExpressionEngine(symbols);
378         assertEquals("/test", engine.attributeKey("", "test"));
379     }
380 
381     /**
382      * Tests obtaining keys for nodes that contain the delimiter character.
383      */
384     @Test
385     public void testNodeKeyWithEscapedDelimiters() {
386         final ImmutableNode node = root.getChildren().get(1);
387         assertEquals("connection..settings", engine.nodeKey(node, "", handler));
388         assertEquals("connection..settings.usr..name", engine.nodeKey(node.getChildren().get(0), engine.nodeKey(node, "", handler), handler));
389     }
390 
391     /**
392      * Tests obtaining keys if the root node is involved.
393      */
394     @Test
395     public void testNodeKeyWithRoot() {
396         assertEquals("", engine.nodeKey(root, null, handler));
397         assertEquals("test", engine.nodeKey(root, "test", handler));
398     }
399 
400     /**
401      * Tests adding new attributes.
402      */
403     @Test
404     public void testPrepareAddAttribute() {
405         final NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables.table(0)[@tableSpace]", handler);
406         assertEquals(tables[0], data.getParent().getChildren().get(0).getValue());
407         assertEquals("tableSpace", data.getNewNodeName());
408         assertTrue(data.isAttribute());
409         assertTrue(data.getPathNodes().isEmpty());
410     }
411 
412     /**
413      * Tests whether an attribute to the root node can be added.
414      */
415     @Test
416     public void testPrepareAddAttributeRoot() {
417         final NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "[@newAttr]", handler);
418         assertSame(root, data.getParent());
419         assertEquals("newAttr", data.getNewNodeName());
420         assertTrue(data.isAttribute());
421     }
422 
423     /**
424      * Tests adding direct child nodes to the existing hierarchy.
425      */
426     @Test
427     public void testPrepareAddDirectly() {
428         NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "newNode", handler);
429         assertSame(root, data.getParent());
430         assertTrue(data.getPathNodes().isEmpty());
431         assertEquals("newNode", data.getNewNodeName());
432         assertFalse(data.isAttribute());
433 
434         data = engine.prepareAdd(root, "tables.table.fields.field.name", handler);
435         assertEquals("name", data.getNewNodeName());
436         assertTrue(data.getPathNodes().isEmpty());
437         assertEquals("field", data.getParent().getNodeName());
438         final ImmutableNode nd = data.getParent().getChildren().get(0);
439         assertEquals("name", nd.getNodeName());
440         assertEquals("version", nd.getValue());
441     }
442 
443     @Test
444     public void testPrepareAddEmptyKey() {
445         assertThrows(IllegalArgumentException.class, () -> engine.prepareAdd(root, "", handler));
446     }
447 
448     /**
449      * Tests using invalid keys, e.g. if something should be added to attributes.
450      */
451     @Test
452     public void testPrepareAddInvalidKey() {
453         assertThrows(IllegalArgumentException.class, () -> engine.prepareAdd(root, "tables.table(0)[@type].new", handler));
454     }
455 
456     @Test
457     public void testPrepareAddInvalidKeyAttribute() {
458         assertThrows(IllegalArgumentException.class,
459                 () -> engine.prepareAdd(root, "a.complete.new.path.with.an[@attribute].at.a.non.allowed[@position]", handler));
460     }
461 
462     @Test
463     public void testPrepareAddNullKey() {
464         assertThrows(IllegalArgumentException.class, () -> engine.prepareAdd(root, null, handler));
465     }
466 
467     /**
468      * Tests whether the node matcher is used when adding keys.
469      */
470     @Test
471     public void testPrepareAddWithAlternativeMatcher() {
472         setUpAlternativeMatcher();
473         final NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables_.table._fields__._field.name", handler);
474         assertEquals("name", data.getNewNodeName());
475         assertTrue(data.getPathNodes().isEmpty());
476     }
477 
478     /**
479      * Tests add operations when an alternative syntax is set.
480      */
481     @Test
482     public void testPrepareAddWithAlternativeSyntax() {
483         setUpAlternativeSyntax();
484         NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables/table[0]/test", handler);
485         assertEquals("test", data.getNewNodeName());
486         assertFalse(data.isAttribute());
487         assertEquals(tables[0], data.getParent().getChildren().get(0).getValue());
488 
489         data = engine.prepareAdd(root, "a/complete/new/path@attr", handler);
490         assertEquals("attr", data.getNewNodeName());
491         checkNodePath(data, "a", "complete", "new", "path");
492         assertSame(root, data.getParent());
493     }
494 
495     /**
496      * Tests adding if indices are involved.
497      */
498     @Test
499     public void testPrepareAddWithIndex() {
500         NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables.table(0).tableSpace", handler);
501         assertEquals("tableSpace", data.getNewNodeName());
502         assertTrue(data.getPathNodes().isEmpty());
503         assertEquals("table", data.getParent().getNodeName());
504         final ImmutableNode node = data.getParent().getChildren().get(0);
505         assertEquals(tables[0], node.getValue());
506 
507         data = engine.prepareAdd(root, "tables.table(1).fields.field(2).alias", handler);
508         assertEquals("alias", data.getNewNodeName());
509         assertEquals("field", data.getParent().getNodeName());
510         assertEquals("creationDate", data.getParent().getChildren().get(0).getValue());
511     }
512 
513     /**
514      * Tests add operations where complete paths are added.
515      */
516     @Test
517     public void testPrepareAddWithPath() {
518         NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables.table(1).fields.field(-1).name", handler);
519         assertEquals("name", data.getNewNodeName());
520         checkNodePath(data, "field");
521         assertEquals("fields", data.getParent().getNodeName());
522 
523         data = engine.prepareAdd(root, "tables.table(-1).name", handler);
524         assertEquals("name", data.getNewNodeName());
525         checkNodePath(data, "table");
526         assertEquals("tables", data.getParent().getNodeName());
527 
528         data = engine.prepareAdd(root, "a.complete.new.path", handler);
529         assertEquals("path", data.getNewNodeName());
530         checkNodePath(data, "a", "complete", "new");
531         assertSame(root, data.getParent());
532     }
533 
534     /**
535      * Tests add operations if property and attribute delimiters are equal. Then it is not possible to add new attribute
536      * nodes.
537      */
538     @Test
539     public void testPrepareAddWithSameAttributeDelimiter() {
540         final DefaultExpressionEngineSymbols symbols = new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS)
541             .setAttributeEnd(null).setAttributeStart(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS.getPropertyDelimiter()).create();
542         engine = new DefaultExpressionEngine(symbols);
543 
544         NodeAddData<ImmutableNode> data = engine.prepareAdd(root, "tables.table(0).test", handler);
545         assertEquals("test", data.getNewNodeName());
546         assertFalse(data.isAttribute());
547         assertEquals("table", data.getParent().getNodeName());
548 
549         data = engine.prepareAdd(root, "a.complete.new.path", handler);
550         assertFalse(data.isAttribute());
551         checkNodePath(data, "a", "complete", "new");
552     }
553 
554     /**
555      * Tests a different query syntax. Sets other strings for the typical tokens used by the expression engine.
556      */
557     @Test
558     public void testQueryAlternativeSyntax() {
559         setUpAlternativeSyntax();
560         checkKeyValue("tables/table[1]/name", "name", tables[1]);
561         checkAttributeValue("tables/table[0]@type", "type", tabTypes[0]);
562         checkAttributeValue("@test", "test", "true");
563         checkKeyValue("connection.settings/usr.name", "usr.name", "scott");
564     }
565 
566     /**
567      * Tests some queries when the same delimiter is used for properties and attributes.
568      */
569     @Test
570     public void testQueryAttributeEmulation() {
571         final DefaultExpressionEngineSymbols symbols = new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS)
572             .setAttributeEnd(null).setAttributeStart(DefaultExpressionEngineSymbols.DEFAULT_PROPERTY_DELIMITER).create();
573         engine = new DefaultExpressionEngine(symbols);
574         checkKeyValue("tables.table(0).name", "name", tables[0]);
575         checkAttributeValue("tables.table(0).type", "type", tabTypes[0]);
576         checkKey("tables.table.type", "type", 2);
577     }
578 
579     /**
580      * Tests querying nodes whose names contain a delimiter.
581      */
582     @Test
583     public void testQueryEscapedKeys() {
584         checkKeyValue("connection..settings.usr..name", "usr.name", "scott");
585         checkKeyValue("connection..settings.usr..pwd", "usr.pwd", "tiger");
586     }
587 
588     /**
589      * Tests some simple queries.
590      */
591     @Test
592     public void testQueryKeys() {
593         checkKey("tables.table.name", "name", 2);
594         checkKey("tables.table.fields.field.name", "name", 10);
595         checkKey("tables.table[@type]", "type", 2);
596         checkKey("tables.table(0).fields.field.name", "name", 5);
597         checkKey("tables.table(1).fields.field.name", "name", 5);
598         checkKey("tables.table.fields.field(1).name", "name", 2);
599     }
600 
601     /**
602      * Tests whether the node matcher is used when querying keys.
603      */
604     @Test
605     public void testQueryKeyWithAlternativeMatcher() {
606         setUpAlternativeMatcher();
607         checkKey("tables_._table_.name_", "name", 2);
608     }
609 
610     /**
611      * Performs some queries and evaluates the values of the result nodes.
612      */
613     @Test
614     public void testQueryNodes() {
615         for (int i = 0; i < tables.length; i++) {
616             checkKeyValue("tables.table(" + i + ").name", "name", tables[i]);
617             checkAttributeValue("tables.table(" + i + ")[@type]", "type", tabTypes[i]);
618 
619             for (int j = 0; j < fields[i].length; j++) {
620                 checkKeyValue("tables.table(" + i + ").fields.field(" + j + ").name", "name", fields[i][j]);
621             }
622         }
623     }
624 
625     /**
626      * Tests querying keys that do not exist.
627      */
628     @Test
629     public void testQueryNonExistingKeys() {
630         checkKey("tables.tablespace.name", null, 0);
631         checkKey("tables.table(2).name", null, 0);
632         checkKey("a complete unknown key", null, 0);
633         checkKey("tables.table(0).fields.field(-1).name", null, 0);
634         checkKey("tables.table(0).fields.field(28).name", null, 0);
635         checkKey("tables.table(0).fields.field().name", null, 0);
636         checkKey("connection.settings.usr.name", null, 0);
637         checkKey("tables.table(0)[@type].additional", null, 0);
638     }
639 
640     /**
641      * Tests whether an attribute of the root node can be queried.
642      */
643     @Test
644     public void testQueryRootAttribute() {
645         checkAttributeValue("[@test]", "test", "true");
646     }
647 
648     /**
649      * Tests whether the root node can be retrieved using the empty key.
650      */
651     @Test
652     public void testQueryRootNodeEmptyKey() {
653         checkQueryRootNode("");
654     }
655 
656     /**
657      * Tests whether the root node can be retrieved using the null key.
658      */
659     @Test
660     public void testQueryRootNodeNullKey() {
661         checkQueryRootNode(null);
662     }
663 }