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.configuration.tree;
18  
19  import java.util.Iterator;
20  import java.util.List;
21  
22  import junit.framework.TestCase;
23  
24  /***
25   * Test class for DefaultExpressionEngine.
26   *
27   * @author Oliver Heger
28   */
29  public class TestDefaultExpressionEngine extends TestCase
30  {
31      /*** Stores the names of the test nodes representing tables. */
32      private static String[] tables =
33      { "users", "documents"};
34  
35      /*** Stores the types of the test table nodes. */
36      private static String[] tabTypes =
37      { "system", "application"};
38  
39      /*** Test data fields for the node hierarchy. */
40      private static String[][] fields =
41      {
42      { "uid", "uname", "firstName", "lastName", "email"},
43      { "docid", "name", "creationDate", "authorID", "version"}};
44  
45      /*** The object to be tested. */
46      DefaultExpressionEngine engine;
47  
48      /*** The root of a hierarchy with configuration nodes. */
49      ConfigurationNode root;
50  
51      protected void setUp() throws Exception
52      {
53          super.setUp();
54          root = setUpNodes();
55          engine = new DefaultExpressionEngine();
56      }
57  
58      /***
59       * Tests some simple queries.
60       */
61      public void testQueryKeys()
62      {
63          checkKey("tables.table.name", "name", 2);
64          checkKey("tables.table.fields.field.name", "name", 10);
65          checkKey("tables.table[@type]", "type", 2);
66          checkKey("tables.table(0).fields.field.name", "name", 5);
67          checkKey("tables.table(1).fields.field.name", "name", 5);
68          checkKey("tables.table.fields.field(1).name", "name", 2);
69      }
70  
71      /***
72       * Performs some queries and evaluates the values of the result nodes.
73       */
74      public void testQueryNodes()
75      {
76          for (int i = 0; i < tables.length; i++)
77          {
78              checkKeyValue("tables.table(" + i + ").name", "name", tables[i]);
79              checkKeyValue("tables.table(" + i + ")[@type]", "type", tabTypes[i]);
80  
81              for (int j = 0; j < fields[i].length; j++)
82              {
83                  checkKeyValue("tables.table(" + i + ").fields.field(" + j
84                          + ").name", "name", fields[i][j]);
85              }
86          }
87      }
88  
89      /***
90       * Tests querying keys that do not exist.
91       */
92      public void testQueryNonExistingKeys()
93      {
94          checkKey("tables.tablespace.name", null, 0);
95          checkKey("tables.table(2).name", null, 0);
96          checkKey("a complete unknown key", null, 0);
97          checkKey("tables.table(0).fields.field(-1).name", null, 0);
98          checkKey("tables.table(0).fields.field(28).name", null, 0);
99          checkKey("tables.table(0).fields.field().name", null, 0);
100         checkKey("connection.settings.usr.name", null, 0);
101     }
102 
103     /***
104      * Tests querying nodes whose names contain a delimiter.
105      */
106     public void testQueryEscapedKeys()
107     {
108         checkKeyValue("connection..settings.usr..name", "usr.name", "scott");
109         checkKeyValue("connection..settings.usr..pwd", "usr.pwd", "tiger");
110     }
111 
112     /***
113      * Tests some queries when the same delimiter is used for properties and
114      * attributes.
115      */
116     public void testQueryAttributeEmulation()
117     {
118         engine.setAttributeEnd(null);
119         engine.setAttributeStart(engine.getPropertyDelimiter());
120         checkKeyValue("tables.table(0).name", "name", tables[0]);
121         checkKeyValue("tables.table(0).type", "type", tabTypes[0]);
122         checkKey("tables.table.type", "type", 2);
123     }
124 
125     /***
126      * Tests accessing the root node.
127      */
128     public void testQueryRootNode()
129     {
130         List nodes = checkKey(null, null, 1);
131         assertSame("Root node not found", root, nodes.get(0));
132         nodes = checkKey("", null, 1);
133         assertSame("Root node not found", root, nodes.get(0));
134         checkKeyValue("[@test]", "test", "true");
135     }
136 
137     /***
138      * Tests a different query snytax. Sets other strings for the typical tokens
139      * used by the expression engine.
140      */
141     public void testQueryAlternativeSyntax()
142     {
143         setUpAlternativeSyntax();
144         checkKeyValue("tables/table[1]/name", "name", tables[1]);
145         checkKeyValue("tables/table[0]@type", "type", tabTypes[0]);
146         checkKeyValue("@test", "test", "true");
147         checkKeyValue("connection.settings/usr.name", "usr.name", "scott");
148     }
149 
150     /***
151      * Tests obtaining keys for nodes.
152      */
153     public void testNodeKey()
154     {
155         ConfigurationNode node = root.getChild(0);
156         assertEquals("Invalid name for descendant of root", "tables", engine
157                 .nodeKey(node, ""));
158         assertEquals("Parent key not respected", "test.tables", engine.nodeKey(
159                 node, "test"));
160         assertEquals("Full parent key not taken into account",
161                 "a.full.parent.key.tables", engine.nodeKey(node,
162                         "a.full.parent.key"));
163     }
164 
165     /***
166      * Tests obtaining keys when the root node is involved.
167      */
168     public void testNodeKeyWithRoot()
169     {
170         assertEquals("Wrong name for root noot", "", engine.nodeKey(root, null));
171         assertEquals("Null name not detected", "test", engine.nodeKey(root,
172                 "test"));
173     }
174 
175     /***
176      * Tests obtaining keys for attribute nodes.
177      */
178     public void testNodeKeyWithAttribute()
179     {
180         ConfigurationNode node = root.getChild(0).getChild(0).getAttribute(0);
181         assertEquals("Wrong attribute node", "type", node.getName());
182         assertEquals("Wrong attribute key", "tables.table[@type]", engine
183                 .nodeKey(node, "tables.table"));
184         assertEquals("Wrong key for root attribute", "[@test]", engine.nodeKey(
185                 root.getAttribute(0), ""));
186     }
187 
188     /***
189      * Tests obtaining keys for nodes that contain the delimiter character.
190      */
191     public void testNodeKeyWithEscapedDelimiters()
192     {
193         ConfigurationNode node = root.getChild(1);
194         assertEquals("Wrong escaped key", "connection..settings", engine
195                 .nodeKey(node, ""));
196         assertEquals("Wrong complex escaped key",
197                 "connection..settings.usr..name", engine.nodeKey(node
198                         .getChild(0), engine.nodeKey(node, "")));
199     }
200 
201     /***
202      * Tests obtaining node keys when a different syntax is set.
203      */
204     public void testNodeKeyWithAlternativeSyntax()
205     {
206         setUpAlternativeSyntax();
207         assertEquals("Wrong child key", "tables/table", engine.nodeKey(root
208                 .getChild(0).getChild(0), "tables"));
209         assertEquals("Wrong attribute key", "@test", engine.nodeKey(root
210                 .getAttribute(0), ""));
211 
212         engine.setAttributeStart(engine.getPropertyDelimiter());
213         assertEquals("Wrong attribute key", "/test", engine.nodeKey(root
214                 .getAttribute(0), ""));
215     }
216 
217     /***
218      * Tests adding direct child nodes to the existing hierarchy.
219      */
220     public void testPrepareAddDirectly()
221     {
222         NodeAddData data = engine.prepareAdd(root, "newNode");
223         assertSame("Wrong parent node", root, data.getParent());
224         assertTrue("Path nodes available", data.getPathNodes().isEmpty());
225         assertEquals("Wrong name of new node", "newNode", data.getNewNodeName());
226         assertFalse("New node is an attribute", data.isAttribute());
227 
228         data = engine.prepareAdd(root, "tables.table.fields.field.name");
229         assertEquals("Wrong name of new node", "name", data.getNewNodeName());
230         assertTrue("Path nodes available", data.getPathNodes().isEmpty());
231         assertEquals("Wrong parent node", "field", data.getParent().getName());
232         ConfigurationNode nd = data.getParent().getChild(0);
233         assertEquals("Field has no name node", "name", nd.getName());
234         assertEquals("Incorrect name", "version", nd.getValue());
235     }
236 
237     /***
238      * Tests adding when indices are involved.
239      */
240     public void testPrepareAddWithIndex()
241     {
242         NodeAddData data = engine
243                 .prepareAdd(root, "tables.table(0).tableSpace");
244         assertEquals("Wrong name of new node", "tableSpace", data
245                 .getNewNodeName());
246         assertTrue("Path nodes available", data.getPathNodes().isEmpty());
247         assertEquals("Wrong type of parent node", "table", data.getParent()
248                 .getName());
249         ConfigurationNode node = data.getParent().getChild(0);
250         assertEquals("Wrong table", tables[0], node.getValue());
251 
252         data = engine.prepareAdd(root, "tables.table(1).fields.field(2).alias");
253         assertEquals("Wrong name of new node", "alias", data.getNewNodeName());
254         assertEquals("Wrong type of parent node", "field", data.getParent()
255                 .getName());
256         assertEquals("Wrong field node", "creationDate", data.getParent()
257                 .getChild(0).getValue());
258     }
259 
260     /***
261      * Tests adding new attributes.
262      */
263     public void testPrepareAddAttribute()
264     {
265         NodeAddData data = engine.prepareAdd(root,
266                 "tables.table(0)[@tableSpace]");
267         assertEquals("Wrong table node", tables[0], data.getParent()
268                 .getChild(0).getValue());
269         assertEquals("Wrong name of new node", "tableSpace", data
270                 .getNewNodeName());
271         assertTrue("Attribute not detected", data.isAttribute());
272         assertTrue("Path nodes available", data.getPathNodes().isEmpty());
273 
274         data = engine.prepareAdd(root, "[@newAttr]");
275         assertSame("Root node is not parent", root, data.getParent());
276         assertEquals("Wrong name of new node", "newAttr", data.getNewNodeName());
277         assertTrue("Attribute not detected", data.isAttribute());
278     }
279 
280     /***
281      * Tests add operations where complete pathes are added.
282      */
283     public void testPrepareAddWithPath()
284     {
285         NodeAddData data = engine.prepareAdd(root,
286                 "tables.table(1).fields.field(-1).name");
287         assertEquals("Wrong name of new node", "name", data.getNewNodeName());
288         checkNodePath(data, new String[]
289         { "field"});
290         assertEquals("Wrong type of parent node", "fields", data.getParent()
291                 .getName());
292 
293         data = engine.prepareAdd(root, "tables.table(-1).name");
294         assertEquals("Wrong name of new node", "name", data.getNewNodeName());
295         checkNodePath(data, new String[]
296         { "table"});
297         assertEquals("Wrong type of parent node", "tables", data.getParent()
298                 .getName());
299 
300         data = engine.prepareAdd(root, "a.complete.new.path");
301         assertEquals("Wrong name of new node", "path", data.getNewNodeName());
302         checkNodePath(data, new String[]
303         { "a", "complete", "new"});
304         assertSame("Root is not parent", root, data.getParent());
305     }
306 
307     /***
308      * Tests add operations when property and attribute delimiters are equal.
309      * Then it is not possible to add new attribute nodes.
310      */
311     public void testPrepareAddWithSameAttributeDelimiter()
312     {
313         engine.setAttributeEnd(null);
314         engine.setAttributeStart(engine.getPropertyDelimiter());
315 
316         NodeAddData data = engine.prepareAdd(root, "tables.table(0).test");
317         assertEquals("Wrong name of new node", "test", data.getNewNodeName());
318         assertFalse("New node is an attribute", data.isAttribute());
319         assertEquals("Wrong type of parent node", "table", data.getParent()
320                 .getName());
321 
322         data = engine.prepareAdd(root, "a.complete.new.path");
323         assertFalse("New node is an attribute", data.isAttribute());
324         checkNodePath(data, new String[]
325         { "a", "complete", "new"});
326     }
327 
328     /***
329      * Tests add operations when an alternative syntax is set.
330      */
331     public void testPrepareAddWithAlternativeSyntax()
332     {
333         setUpAlternativeSyntax();
334         NodeAddData data = engine.prepareAdd(root, "tables/table[0]/test");
335         assertEquals("Wrong name of new node", "test", data.getNewNodeName());
336         assertFalse("New node is attribute", data.isAttribute());
337         assertEquals("Wrong parent node", tables[0], data.getParent().getChild(
338                 0).getValue());
339 
340         data = engine.prepareAdd(root, "a/complete/new/path@attr");
341         assertEquals("Wrong name of new attribute", "attr", data
342                 .getNewNodeName());
343         checkNodePath(data, new String[]
344         { "a", "complete", "new", "path"});
345         assertSame("Root is not parent", root, data.getParent());
346     }
347 
348     /***
349      * Tests using invalid keys, e.g. if something should be added to
350      * attributes.
351      */
352     public void testPrepareAddInvalidKeys()
353     {
354         try
355         {
356             engine.prepareAdd(root, "tables.table(0)[@type].new");
357             fail("Could add node to existing attribute!");
358         }
359         catch (IllegalArgumentException iex)
360         {
361             // ok
362         }
363 
364         try
365         {
366             engine
367                     .prepareAdd(root,
368                             "a.complete.new.path.with.an[@attribute].at.a.non.allowed[@position]");
369             fail("Could add invalid path!");
370         }
371         catch (IllegalArgumentException iex)
372         {
373             // ok
374         }
375 
376         try
377         {
378             engine.prepareAdd(root, null);
379             fail("Could add null key!");
380         }
381         catch (IllegalArgumentException iex)
382         {
383             // ok
384         }
385 
386         try
387         {
388             engine.prepareAdd(root, "");
389             fail("Could add undefined key!");
390         }
391         catch (IllegalArgumentException iex)
392         {
393             // ok
394         }
395     }
396 
397     /***
398      * Creates a node hierarchy for testing that consists of tables, their
399      * fields, and some additional data:
400      *
401      * <pre>
402      *  tables
403      *       table
404      *          name
405      *          fields
406      *              field
407      *                  name
408      *              field
409      *                  name
410      * </pre>
411      *
412      * @return the root of the test node hierarchy
413      */
414     protected ConfigurationNode setUpNodes()
415     {
416         DefaultConfigurationNode rootNode = new DefaultConfigurationNode();
417 
418         DefaultConfigurationNode nodeTables = new DefaultConfigurationNode(
419                 "tables");
420         rootNode.addChild(nodeTables);
421         for (int i = 0; i < tables.length; i++)
422         {
423             DefaultConfigurationNode nodeTable = new DefaultConfigurationNode(
424                     "table");
425             nodeTables.addChild(nodeTable);
426             nodeTable.addChild(new DefaultConfigurationNode("name", tables[i]));
427             nodeTable.addAttribute(new DefaultConfigurationNode("type",
428                     tabTypes[i]));
429             DefaultConfigurationNode nodeFields = new DefaultConfigurationNode(
430                     "fields");
431             nodeTable.addChild(nodeFields);
432 
433             for (int j = 0; j < fields[i].length; j++)
434             {
435                 nodeFields.addChild(createFieldNode(fields[i][j]));
436             }
437         }
438 
439         DefaultConfigurationNode nodeConn = new DefaultConfigurationNode(
440                 "connection.settings");
441         rootNode.addChild(nodeConn);
442         nodeConn.addChild(new DefaultConfigurationNode("usr.name", "scott"));
443         nodeConn.addChild(new DefaultConfigurationNode("usr.pwd", "tiger"));
444         rootNode.addAttribute(new DefaultConfigurationNode("test", "true"));
445 
446         return rootNode;
447     }
448 
449     /***
450      * Configures the expression engine to use a different syntax.
451      */
452     private void setUpAlternativeSyntax()
453     {
454         engine.setAttributeEnd(null);
455         engine.setAttributeStart("@");
456         engine.setPropertyDelimiter("/");
457         engine.setEscapedDelimiter(null);
458         engine.setIndexStart("[");
459         engine.setIndexEnd("]");
460     }
461 
462     /***
463      * Helper method for checking the evaluation of a key. Queries the
464      * expression engine and tests if the expected results are returned.
465      *
466      * @param key the key
467      * @param name the name of the nodes to be returned
468      * @param count the number of expected result nodes
469      * @return the list with the results of the query
470      */
471     private List checkKey(String key, String name, int count)
472     {
473         List nodes = engine.query(root, key);
474         assertEquals("Wrong number of result nodes for key " + key, count,
475                 nodes.size());
476         for (Iterator it = nodes.iterator(); it.hasNext();)
477         {
478             assertEquals("Wrong result node for key " + key, name,
479                     ((ConfigurationNode) it.next()).getName());
480         }
481         return nodes;
482     }
483 
484     /***
485      * Helper method for checking the value of a node specified by the given
486      * key. This method evaluates the key and checks whether the resulting node
487      * has the expected value.
488      *
489      * @param key the key
490      * @param name the expected name of the result node
491      * @param value the expected value of the result node
492      */
493     private void checkKeyValue(String key, String name, String value)
494     {
495         List nodes = checkKey(key, name, 1);
496         assertEquals("Wrong value for key " + key, value,
497                 ((ConfigurationNode) nodes.get(0)).getValue());
498     }
499 
500     /***
501      * Helper method for checking the path of an add operation.
502      *
503      * @param data the add data object
504      * @param expected the expected path nodes
505      */
506     private void checkNodePath(NodeAddData data, String[] expected)
507     {
508         assertEquals("Wrong number of path nodes", expected.length, data
509                 .getPathNodes().size());
510         Iterator it = data.getPathNodes().iterator();
511         for (int i = 0; i < expected.length; i++)
512         {
513             assertEquals("Wrong path node " + i, expected[i], it.next());
514         }
515     }
516 
517     /***
518      * Helper method for creating a field node with its children for the test
519      * node hierarchy.
520      *
521      * @param name the name of the field
522      * @return the field node
523      */
524     private static ConfigurationNode createFieldNode(String name)
525     {
526         DefaultConfigurationNode nodeField = new DefaultConfigurationNode(
527                 "field");
528         nodeField.addChild(new DefaultConfigurationNode("name", name));
529         return nodeField;
530     }
531 }