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;
018    
019    import static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertFalse;
021    import static org.junit.Assert.assertSame;
022    import static org.junit.Assert.assertTrue;
023    
024    import java.util.Iterator;
025    import java.util.List;
026    
027    import org.junit.Before;
028    import org.junit.Test;
029    
030    /**
031     * Test class for DefaultExpressionEngine.
032     *
033     * @author <a
034     * href="http://commons.apache.org/configuration/team-list.html">Commons
035     * Configuration team</a>
036     * @version $Id: TestDefaultExpressionEngine.java 1225918 2011-12-30 20:54:47Z oheger $
037     */
038    public class TestDefaultExpressionEngine
039    {
040        /** Stores the names of the test nodes representing tables. */
041        private static String[] tables =
042        { "users", "documents"};
043    
044        /** Stores the types of the test table nodes. */
045        private static String[] tabTypes =
046        { "system", "application"};
047    
048        /** Test data fields for the node hierarchy. */
049        private static String[][] fields =
050        {
051        { "uid", "uname", "firstName", "lastName", "email"},
052        { "docid", "name", "creationDate", "authorID", "version"}};
053    
054        /** The object to be tested. */
055        DefaultExpressionEngine engine;
056    
057        /** The root of a hierarchy with configuration nodes. */
058        ConfigurationNode root;
059    
060        @Before
061        public void setUp() throws Exception
062        {
063            root = setUpNodes();
064            engine = new DefaultExpressionEngine();
065        }
066    
067        /**
068         * Tests some simple queries.
069         */
070        @Test
071        public void testQueryKeys()
072        {
073            checkKey("tables.table.name", "name", 2);
074            checkKey("tables.table.fields.field.name", "name", 10);
075            checkKey("tables.table[@type]", "type", 2);
076            checkKey("tables.table(0).fields.field.name", "name", 5);
077            checkKey("tables.table(1).fields.field.name", "name", 5);
078            checkKey("tables.table.fields.field(1).name", "name", 2);
079        }
080    
081        /**
082         * Performs some queries and evaluates the values of the result nodes.
083         */
084        @Test
085        public void testQueryNodes()
086        {
087            for (int i = 0; i < tables.length; i++)
088            {
089                checkKeyValue("tables.table(" + i + ").name", "name", tables[i]);
090                checkKeyValue("tables.table(" + i + ")[@type]", "type", tabTypes[i]);
091    
092                for (int j = 0; j < fields[i].length; j++)
093                {
094                    checkKeyValue("tables.table(" + i + ").fields.field(" + j
095                            + ").name", "name", fields[i][j]);
096                }
097            }
098        }
099    
100        /**
101         * Tests querying keys that do not exist.
102         */
103        @Test
104        public void testQueryNonExistingKeys()
105        {
106            checkKey("tables.tablespace.name", null, 0);
107            checkKey("tables.table(2).name", null, 0);
108            checkKey("a complete unknown key", null, 0);
109            checkKey("tables.table(0).fields.field(-1).name", null, 0);
110            checkKey("tables.table(0).fields.field(28).name", null, 0);
111            checkKey("tables.table(0).fields.field().name", null, 0);
112            checkKey("connection.settings.usr.name", null, 0);
113        }
114    
115        /**
116         * Tests querying nodes whose names contain a delimiter.
117         */
118        @Test
119        public void testQueryEscapedKeys()
120        {
121            checkKeyValue("connection..settings.usr..name", "usr.name", "scott");
122            checkKeyValue("connection..settings.usr..pwd", "usr.pwd", "tiger");
123        }
124    
125        /**
126         * Tests some queries when the same delimiter is used for properties and
127         * attributes.
128         */
129        @Test
130        public void testQueryAttributeEmulation()
131        {
132            engine.setAttributeEnd(null);
133            engine.setAttributeStart(engine.getPropertyDelimiter());
134            checkKeyValue("tables.table(0).name", "name", tables[0]);
135            checkKeyValue("tables.table(0).type", "type", tabTypes[0]);
136            checkKey("tables.table.type", "type", 2);
137        }
138    
139        /**
140         * Tests accessing the root node.
141         */
142        @Test
143        public void testQueryRootNode()
144        {
145            List<ConfigurationNode> nodes = checkKey(null, null, 1);
146            assertSame("Root node not found", root, nodes.get(0));
147            nodes = checkKey("", null, 1);
148            assertSame("Root node not found", root, nodes.get(0));
149            checkKeyValue("[@test]", "test", "true");
150        }
151    
152        /**
153         * Tests a different query syntax. Sets other strings for the typical tokens
154         * used by the expression engine.
155         */
156        @Test
157        public void testQueryAlternativeSyntax()
158        {
159            setUpAlternativeSyntax();
160            checkKeyValue("tables/table[1]/name", "name", tables[1]);
161            checkKeyValue("tables/table[0]@type", "type", tabTypes[0]);
162            checkKeyValue("@test", "test", "true");
163            checkKeyValue("connection.settings/usr.name", "usr.name", "scott");
164        }
165    
166        /**
167         * Tests obtaining keys for nodes.
168         */
169        @Test
170        public void testNodeKey()
171        {
172            ConfigurationNode node = root.getChild(0);
173            assertEquals("Invalid name for descendant of root", "tables", engine
174                    .nodeKey(node, ""));
175            assertEquals("Parent key not respected", "test.tables", engine.nodeKey(
176                    node, "test"));
177            assertEquals("Full parent key not taken into account",
178                    "a.full.parent.key.tables", engine.nodeKey(node,
179                            "a.full.parent.key"));
180        }
181    
182        /**
183         * Tests obtaining keys when the root node is involved.
184         */
185        @Test
186        public void testNodeKeyWithRoot()
187        {
188            assertEquals("Wrong name for root noot", "", engine.nodeKey(root, null));
189            assertEquals("Null name not detected", "test", engine.nodeKey(root,
190                    "test"));
191        }
192    
193        /**
194         * Tests obtaining keys for attribute nodes.
195         */
196        @Test
197        public void testNodeKeyWithAttribute()
198        {
199            ConfigurationNode node = root.getChild(0).getChild(0).getAttribute(0);
200            assertEquals("Wrong attribute node", "type", node.getName());
201            assertEquals("Wrong attribute key", "tables.table[@type]", engine
202                    .nodeKey(node, "tables.table"));
203            assertEquals("Wrong key for root attribute", "[@test]", engine.nodeKey(
204                    root.getAttribute(0), ""));
205        }
206    
207        /**
208         * Tests obtaining keys for nodes that contain the delimiter character.
209         */
210        @Test
211        public void testNodeKeyWithEscapedDelimiters()
212        {
213            ConfigurationNode node = root.getChild(1);
214            assertEquals("Wrong escaped key", "connection..settings", engine
215                    .nodeKey(node, ""));
216            assertEquals("Wrong complex escaped key",
217                    "connection..settings.usr..name", engine.nodeKey(node
218                            .getChild(0), engine.nodeKey(node, "")));
219        }
220    
221        /**
222         * Tests obtaining node keys when a different syntax is set.
223         */
224        @Test
225        public void testNodeKeyWithAlternativeSyntax()
226        {
227            setUpAlternativeSyntax();
228            assertEquals("Wrong child key", "tables/table", engine.nodeKey(root
229                    .getChild(0).getChild(0), "tables"));
230            assertEquals("Wrong attribute key", "@test", engine.nodeKey(root
231                    .getAttribute(0), ""));
232    
233            engine.setAttributeStart(engine.getPropertyDelimiter());
234            assertEquals("Wrong attribute key", "/test", engine.nodeKey(root
235                    .getAttribute(0), ""));
236        }
237    
238        /**
239         * Tests adding direct child nodes to the existing hierarchy.
240         */
241        @Test
242        public void testPrepareAddDirectly()
243        {
244            NodeAddData data = engine.prepareAdd(root, "newNode");
245            assertSame("Wrong parent node", root, data.getParent());
246            assertTrue("Path nodes available", data.getPathNodes().isEmpty());
247            assertEquals("Wrong name of new node", "newNode", data.getNewNodeName());
248            assertFalse("New node is an attribute", data.isAttribute());
249    
250            data = engine.prepareAdd(root, "tables.table.fields.field.name");
251            assertEquals("Wrong name of new node", "name", data.getNewNodeName());
252            assertTrue("Path nodes available", data.getPathNodes().isEmpty());
253            assertEquals("Wrong parent node", "field", data.getParent().getName());
254            ConfigurationNode nd = data.getParent().getChild(0);
255            assertEquals("Field has no name node", "name", nd.getName());
256            assertEquals("Incorrect name", "version", nd.getValue());
257        }
258    
259        /**
260         * Tests adding when indices are involved.
261         */
262        @Test
263        public void testPrepareAddWithIndex()
264        {
265            NodeAddData data = engine
266                    .prepareAdd(root, "tables.table(0).tableSpace");
267            assertEquals("Wrong name of new node", "tableSpace", data
268                    .getNewNodeName());
269            assertTrue("Path nodes available", data.getPathNodes().isEmpty());
270            assertEquals("Wrong type of parent node", "table", data.getParent()
271                    .getName());
272            ConfigurationNode node = data.getParent().getChild(0);
273            assertEquals("Wrong table", tables[0], node.getValue());
274    
275            data = engine.prepareAdd(root, "tables.table(1).fields.field(2).alias");
276            assertEquals("Wrong name of new node", "alias", data.getNewNodeName());
277            assertEquals("Wrong type of parent node", "field", data.getParent()
278                    .getName());
279            assertEquals("Wrong field node", "creationDate", data.getParent()
280                    .getChild(0).getValue());
281        }
282    
283        /**
284         * Tests adding new attributes.
285         */
286        @Test
287        public void testPrepareAddAttribute()
288        {
289            NodeAddData data = engine.prepareAdd(root,
290                    "tables.table(0)[@tableSpace]");
291            assertEquals("Wrong table node", tables[0], data.getParent()
292                    .getChild(0).getValue());
293            assertEquals("Wrong name of new node", "tableSpace", data
294                    .getNewNodeName());
295            assertTrue("Attribute not detected", data.isAttribute());
296            assertTrue("Path nodes available", data.getPathNodes().isEmpty());
297    
298            data = engine.prepareAdd(root, "[@newAttr]");
299            assertSame("Root node is not parent", root, data.getParent());
300            assertEquals("Wrong name of new node", "newAttr", data.getNewNodeName());
301            assertTrue("Attribute not detected", data.isAttribute());
302        }
303    
304        /**
305         * Tests add operations where complete paths are added.
306         */
307        @Test
308        public void testPrepareAddWithPath()
309        {
310            NodeAddData data = engine.prepareAdd(root,
311                    "tables.table(1).fields.field(-1).name");
312            assertEquals("Wrong name of new node", "name", data.getNewNodeName());
313            checkNodePath(data, new String[]
314            { "field"});
315            assertEquals("Wrong type of parent node", "fields", data.getParent()
316                    .getName());
317    
318            data = engine.prepareAdd(root, "tables.table(-1).name");
319            assertEquals("Wrong name of new node", "name", data.getNewNodeName());
320            checkNodePath(data, new String[]
321            { "table"});
322            assertEquals("Wrong type of parent node", "tables", data.getParent()
323                    .getName());
324    
325            data = engine.prepareAdd(root, "a.complete.new.path");
326            assertEquals("Wrong name of new node", "path", data.getNewNodeName());
327            checkNodePath(data, new String[]
328            { "a", "complete", "new"});
329            assertSame("Root is not parent", root, data.getParent());
330        }
331    
332        /**
333         * Tests add operations when property and attribute delimiters are equal.
334         * Then it is not possible to add new attribute nodes.
335         */
336        @Test
337        public void testPrepareAddWithSameAttributeDelimiter()
338        {
339            engine.setAttributeEnd(null);
340            engine.setAttributeStart(engine.getPropertyDelimiter());
341    
342            NodeAddData data = engine.prepareAdd(root, "tables.table(0).test");
343            assertEquals("Wrong name of new node", "test", data.getNewNodeName());
344            assertFalse("New node is an attribute", data.isAttribute());
345            assertEquals("Wrong type of parent node", "table", data.getParent()
346                    .getName());
347    
348            data = engine.prepareAdd(root, "a.complete.new.path");
349            assertFalse("New node is an attribute", data.isAttribute());
350            checkNodePath(data, new String[]
351            { "a", "complete", "new"});
352        }
353    
354        /**
355         * Tests add operations when an alternative syntax is set.
356         */
357        @Test
358        public void testPrepareAddWithAlternativeSyntax()
359        {
360            setUpAlternativeSyntax();
361            NodeAddData data = engine.prepareAdd(root, "tables/table[0]/test");
362            assertEquals("Wrong name of new node", "test", data.getNewNodeName());
363            assertFalse("New node is attribute", data.isAttribute());
364            assertEquals("Wrong parent node", tables[0], data.getParent().getChild(
365                    0).getValue());
366    
367            data = engine.prepareAdd(root, "a/complete/new/path@attr");
368            assertEquals("Wrong name of new attribute", "attr", data
369                    .getNewNodeName());
370            checkNodePath(data, new String[]
371            { "a", "complete", "new", "path"});
372            assertSame("Root is not parent", root, data.getParent());
373        }
374    
375        /**
376         * Tests using invalid keys, e.g. if something should be added to
377         * attributes.
378         */
379        @Test(expected = IllegalArgumentException.class)
380        public void testPrepareAddInvalidKey()
381        {
382            engine.prepareAdd(root, "tables.table(0)[@type].new");
383        }
384    
385        @Test(expected = IllegalArgumentException.class)
386        public void testPrepareAddInvalidKeyAttribute()
387        {
388            engine
389            .prepareAdd(root,
390                    "a.complete.new.path.with.an[@attribute].at.a.non.allowed[@position]");
391        }
392    
393        @Test(expected = IllegalArgumentException.class)
394        public void testPrepareAddNullKey()
395        {
396            engine.prepareAdd(root, null);
397        }
398    
399        @Test(expected = IllegalArgumentException.class)
400        public void testPrepareAddEmptyKey()
401        {
402            engine.prepareAdd(root, "");
403        }
404    
405        /**
406         * Creates a node hierarchy for testing that consists of tables, their
407         * fields, and some additional data:
408         *
409         * <pre>
410         *  tables
411         *       table
412         *          name
413         *          fields
414         *              field
415         *                  name
416         *              field
417         *                  name
418         * </pre>
419         *
420         * @return the root of the test node hierarchy
421         */
422        protected ConfigurationNode setUpNodes()
423        {
424            DefaultConfigurationNode rootNode = new DefaultConfigurationNode();
425    
426            DefaultConfigurationNode nodeTables = new DefaultConfigurationNode(
427                    "tables");
428            rootNode.addChild(nodeTables);
429            for (int i = 0; i < tables.length; i++)
430            {
431                DefaultConfigurationNode nodeTable = new DefaultConfigurationNode(
432                        "table");
433                nodeTables.addChild(nodeTable);
434                nodeTable.addChild(new DefaultConfigurationNode("name", tables[i]));
435                nodeTable.addAttribute(new DefaultConfigurationNode("type",
436                        tabTypes[i]));
437                DefaultConfigurationNode nodeFields = new DefaultConfigurationNode(
438                        "fields");
439                nodeTable.addChild(nodeFields);
440    
441                for (int j = 0; j < fields[i].length; j++)
442                {
443                    nodeFields.addChild(createFieldNode(fields[i][j]));
444                }
445            }
446    
447            DefaultConfigurationNode nodeConn = new DefaultConfigurationNode(
448                    "connection.settings");
449            rootNode.addChild(nodeConn);
450            nodeConn.addChild(new DefaultConfigurationNode("usr.name", "scott"));
451            nodeConn.addChild(new DefaultConfigurationNode("usr.pwd", "tiger"));
452            rootNode.addAttribute(new DefaultConfigurationNode("test", "true"));
453    
454            return rootNode;
455        }
456    
457        /**
458         * Configures the expression engine to use a different syntax.
459         */
460        private void setUpAlternativeSyntax()
461        {
462            engine.setAttributeEnd(null);
463            engine.setAttributeStart("@");
464            engine.setPropertyDelimiter("/");
465            engine.setEscapedDelimiter(null);
466            engine.setIndexStart("[");
467            engine.setIndexEnd("]");
468        }
469    
470        /**
471         * Helper method for checking the evaluation of a key. Queries the
472         * expression engine and tests if the expected results are returned.
473         *
474         * @param key the key
475         * @param name the name of the nodes to be returned
476         * @param count the number of expected result nodes
477         * @return the list with the results of the query
478         */
479        private List<ConfigurationNode> checkKey(String key, String name, int count)
480        {
481            List<ConfigurationNode> nodes = engine.query(root, key);
482            assertEquals("Wrong number of result nodes for key " + key, count,
483                    nodes.size());
484            for (Iterator<ConfigurationNode> it = nodes.iterator(); it.hasNext();)
485            {
486                assertEquals("Wrong result node for key " + key, name,
487                        it.next().getName());
488            }
489            return nodes;
490        }
491    
492        /**
493         * Helper method for checking the value of a node specified by the given
494         * key. This method evaluates the key and checks whether the resulting node
495         * has the expected value.
496         *
497         * @param key the key
498         * @param name the expected name of the result node
499         * @param value the expected value of the result node
500         */
501        private void checkKeyValue(String key, String name, String value)
502        {
503            List<ConfigurationNode> nodes = checkKey(key, name, 1);
504            assertEquals("Wrong value for key " + key, value,
505                    nodes.get(0).getValue());
506        }
507    
508        /**
509         * Helper method for checking the path of an add operation.
510         *
511         * @param data the add data object
512         * @param expected the expected path nodes
513         */
514        private void checkNodePath(NodeAddData data, String[] expected)
515        {
516            assertEquals("Wrong number of path nodes", expected.length, data
517                    .getPathNodes().size());
518            Iterator<String> it = data.getPathNodes().iterator();
519            for (int i = 0; i < expected.length; i++)
520            {
521                assertEquals("Wrong path node " + i, expected[i], it.next());
522            }
523        }
524    
525        /**
526         * Helper method for creating a field node with its children for the test
527         * node hierarchy.
528         *
529         * @param name the name of the field
530         * @return the field node
531         */
532        private static ConfigurationNode createFieldNode(String name)
533        {
534            DefaultConfigurationNode nodeField = new DefaultConfigurationNode(
535                    "field");
536            nodeField.addChild(new DefaultConfigurationNode("name", name));
537            return nodeField;
538        }
539    }