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