    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 }