Coverage Report - org.apache.commons.configuration.tree.DefaultExpressionEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultExpressionEngine
100%
72/72
97%
35/36
2,278
 
 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.Collection;
 20  
 import java.util.LinkedList;
 21  
 import java.util.List;
 22  
 
 23  
 import org.apache.commons.lang.StringUtils;
 24  
 
 25  
 /**
 26  
  * <p>
 27  
  * A default implementation of the {@code ExpressionEngine} interface
 28  
  * providing the &quot;native&quote; expression language for hierarchical
 29  
  * configurations.
 30  
  * </p>
 31  
  * <p>
 32  
  * This class implements a rather simple expression language for navigating
 33  
  * through a hierarchy of configuration nodes. It supports the following
 34  
  * operations:
 35  
  * </p>
 36  
  * <p>
 37  
  * <ul>
 38  
  * <li>Navigating from a node to one of its children using the child node
 39  
  * delimiter, which is by the default a dot (&quot;.&quot;).</li>
 40  
  * <li>Navigating from a node to one of its attributes using the attribute node
 41  
  * delimiter, which by default follows the XPATH like syntax
 42  
  * <code>[@&lt;attributeName&gt;]</code>.</li>
 43  
  * <li>If there are multiple child or attribute nodes with the same name, a
 44  
  * specific node can be selected using a numerical index. By default indices are
 45  
  * written in parenthesis.</li>
 46  
  * </ul>
 47  
  * </p>
 48  
  * <p>
 49  
  * As an example consider the following XML document:
 50  
  * </p>
 51  
  *
 52  
  * <pre>
 53  
  *  &lt;database&gt;
 54  
  *    &lt;tables&gt;
 55  
  *      &lt;table type=&quot;system&quot;&gt;
 56  
  *        &lt;name&gt;users&lt;/name&gt;
 57  
  *        &lt;fields&gt;
 58  
  *          &lt;field&gt;
 59  
  *            &lt;name&gt;lid&lt;/name&gt;
 60  
  *            &lt;type&gt;long&lt;/name&gt;
 61  
  *          &lt;/field&gt;
 62  
  *          &lt;field&gt;
 63  
  *            &lt;name&gt;usrName&lt;/name&gt;
 64  
  *            &lt;type&gt;java.lang.String&lt;/type&gt;
 65  
  *          &lt;/field&gt;
 66  
  *         ...
 67  
  *        &lt;/fields&gt;
 68  
  *      &lt;/table&gt;
 69  
  *      &lt;table&gt;
 70  
  *        &lt;name&gt;documents&lt;/name&gt;
 71  
  *        &lt;fields&gt;
 72  
  *          &lt;field&gt;
 73  
  *            &lt;name&gt;docid&lt;/name&gt;
 74  
  *            &lt;type&gt;long&lt;/type&gt;
 75  
  *          &lt;/field&gt;
 76  
  *          ...
 77  
  *        &lt;/fields&gt;
 78  
  *      &lt;/table&gt;
 79  
  *      ...
 80  
  *    &lt;/tables&gt;
 81  
  *  &lt;/database&gt;
 82  
  * </pre>
 83  
  *
 84  
  * </p>
 85  
  * <p>
 86  
  * If this document is parsed and stored in a hierarchical configuration object,
 87  
  * for instance the key {@code tables.table(0).name} can be used to find
 88  
  * out the name of the first table. In opposite {@code tables.table.name}
 89  
  * would return a collection with the names of all available tables. Similarly
 90  
  * the key {@code tables.table(1).fields.field.name} returns a collection
 91  
  * with the names of all fields of the second table. If another index is added
 92  
  * after the {@code field} element, a single field can be accessed:
 93  
  * {@code tables.table(1).fields.field(0).name}. The key
 94  
  * {@code tables.table(0)[@type]} would select the type attribute of the
 95  
  * first table.
 96  
  * </p>
 97  
  * <p>
 98  
  * This example works with the default values for delimiters and index markers.
 99  
  * It is also possible to set custom values for these properties so that you can
 100  
  * adapt a {@code DefaultExpressionEngine} to your personal needs.
 101  
  * </p>
 102  
  *
 103  
  * @since 1.3
 104  
  * @author <a
 105  
  * href="http://commons.apache.org/configuration/team-list.html">Commons
 106  
  * Configuration team</a>
 107  
  * @version $Id: DefaultExpressionEngine.java 1301991 2012-03-17 20:18:02Z sebb $
 108  
  */
 109  88
 public class DefaultExpressionEngine implements ExpressionEngine
 110  
 {
 111  
     /** Constant for the default property delimiter. */
 112  
     public static final String DEFAULT_PROPERTY_DELIMITER = ".";
 113  
 
 114  
     /** Constant for the default escaped property delimiter. */
 115  
     public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
 116  
             + DEFAULT_PROPERTY_DELIMITER;
 117  
 
 118  
     /** Constant for the default attribute start marker. */
 119  
     public static final String DEFAULT_ATTRIBUTE_START = "[@";
 120  
 
 121  
     /** Constant for the default attribute end marker. */
 122  
     public static final String DEFAULT_ATTRIBUTE_END = "]";
 123  
 
 124  
     /** Constant for the default index start marker. */
 125  
     public static final String DEFAULT_INDEX_START = "(";
 126  
 
 127  
     /** Constant for the default index end marker. */
 128  
     public static final String DEFAULT_INDEX_END = ")";
 129  
 
 130  
     /** Stores the property delimiter. */
 131  88
     private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
 132  
 
 133  
     /** Stores the escaped property delimiter. */
 134  88
     private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
 135  
 
 136  
     /** Stores the attribute start marker. */
 137  88
     private String attributeStart = DEFAULT_ATTRIBUTE_START;
 138  
 
 139  
     /** Stores the attribute end marker. */
 140  88
     private String attributeEnd = DEFAULT_ATTRIBUTE_END;
 141  
 
 142  
     /** Stores the index start marker. */
 143  88
     private String indexStart = DEFAULT_INDEX_START;
 144  
 
 145  
     /** stores the index end marker. */
 146  88
     private String indexEnd = DEFAULT_INDEX_END;
 147  
 
 148  
     /**
 149  
      * Sets the attribute end marker.
 150  
      *
 151  
      * @return the attribute end marker
 152  
      */
 153  
     public String getAttributeEnd()
 154  
     {
 155  736418
         return attributeEnd;
 156  
     }
 157  
 
 158  
     /**
 159  
      * Sets the attribute end marker.
 160  
      *
 161  
      * @param attributeEnd the attribute end marker; can be <b>null</b> if no
 162  
      * end marker is needed
 163  
      */
 164  
     public void setAttributeEnd(String attributeEnd)
 165  
     {
 166  8
         this.attributeEnd = attributeEnd;
 167  8
     }
 168  
 
 169  
     /**
 170  
      * Returns the attribute start marker.
 171  
      *
 172  
      * @return the attribute start marker
 173  
      */
 174  
     public String getAttributeStart()
 175  
     {
 176  1498491
         return attributeStart;
 177  
     }
 178  
 
 179  
     /**
 180  
      * Sets the attribute start marker. Attribute start and end marker are used
 181  
      * together to detect attributes in a property key.
 182  
      *
 183  
      * @param attributeStart the attribute start marker
 184  
      */
 185  
     public void setAttributeStart(String attributeStart)
 186  
     {
 187  9
         this.attributeStart = attributeStart;
 188  9
     }
 189  
 
 190  
     /**
 191  
      * Returns the escaped property delimiter string.
 192  
      *
 193  
      * @return the escaped property delimiter
 194  
      */
 195  
     public String getEscapedDelimiter()
 196  
     {
 197  1587539
         return escapedDelimiter;
 198  
     }
 199  
 
 200  
     /**
 201  
      * Sets the escaped property delimiter string. With this string a delimiter
 202  
      * that belongs to the key of a property can be escaped. If for instance
 203  
      * &quot;.&quot; is used as property delimiter, you can set the escaped
 204  
      * delimiter to &quot;\.&quot; and can then escape the delimiter with a back
 205  
      * slash.
 206  
      *
 207  
      * @param escapedDelimiter the escaped delimiter string
 208  
      */
 209  
     public void setEscapedDelimiter(String escapedDelimiter)
 210  
     {
 211  6
         this.escapedDelimiter = escapedDelimiter;
 212  6
     }
 213  
 
 214  
     /**
 215  
      * Returns the index end marker.
 216  
      *
 217  
      * @return the index end marker
 218  
      */
 219  
     public String getIndexEnd()
 220  
     {
 221  867
         return indexEnd;
 222  
     }
 223  
 
 224  
     /**
 225  
      * Sets the index end marker.
 226  
      *
 227  
      * @param indexEnd the index end marker
 228  
      */
 229  
     public void setIndexEnd(String indexEnd)
 230  
     {
 231  25
         this.indexEnd = indexEnd;
 232  25
     }
 233  
 
 234  
     /**
 235  
      * Returns the index start marker.
 236  
      *
 237  
      * @return the index start marker
 238  
      */
 239  
     public String getIndexStart()
 240  
     {
 241  745563
         return indexStart;
 242  
     }
 243  
 
 244  
     /**
 245  
      * Sets the index start marker. Index start and end marker are used together
 246  
      * to detect indices in a property key.
 247  
      *
 248  
      * @param indexStart the index start marker
 249  
      */
 250  
     public void setIndexStart(String indexStart)
 251  
     {
 252  25
         this.indexStart = indexStart;
 253  25
     }
 254  
 
 255  
     /**
 256  
      * Returns the property delimiter.
 257  
      *
 258  
      * @return the property delimiter
 259  
      */
 260  
     public String getPropertyDelimiter()
 261  
     {
 262  3757097
         return propertyDelimiter;
 263  
     }
 264  
 
 265  
     /**
 266  
      * Sets the property delimiter. This string is used to split the parts of a
 267  
      * property key.
 268  
      *
 269  
      * @param propertyDelimiter the property delimiter
 270  
      */
 271  
     public void setPropertyDelimiter(String propertyDelimiter)
 272  
     {
 273  22
         this.propertyDelimiter = propertyDelimiter;
 274  22
     }
 275  
 
 276  
     /**
 277  
      * Evaluates the given key and returns all matching nodes. This method
 278  
      * supports the syntax as described in the class comment.
 279  
      *
 280  
      * @param root the root node
 281  
      * @param key the key
 282  
      * @return a list with the matching nodes
 283  
      */
 284  
     public List<ConfigurationNode> query(ConfigurationNode root, String key)
 285  
     {
 286  713573
         List<ConfigurationNode> nodes = new LinkedList<ConfigurationNode>();
 287  713572
         findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
 288  
                 root, nodes);
 289  713569
         return nodes;
 290  
     }
 291  
 
 292  
     /**
 293  
      * Determines the key of the passed in node. This implementation takes the
 294  
      * given parent key, adds a property delimiter, and then adds the node's
 295  
      * name. (For attribute nodes the attribute delimiters are used instead.)
 296  
      * The name of the root node is a blanc string. Note that no indices will be
 297  
      * returned.
 298  
      *
 299  
      * @param node the node whose key is to be determined
 300  
      * @param parentKey the key of this node's parent
 301  
      * @return the key for the given node
 302  
      */
 303  
     public String nodeKey(ConfigurationNode node, String parentKey)
 304  
     {
 305  3117
         if (parentKey == null)
 306  
         {
 307  
             // this is the root node
 308  78
             return StringUtils.EMPTY;
 309  
         }
 310  
 
 311  
         else
 312  
         {
 313  3039
             DefaultConfigurationKey key = new DefaultConfigurationKey(this,
 314  
                     parentKey);
 315  3039
             if (node.isAttribute())
 316  
             {
 317  803
                 key.appendAttribute(node.getName());
 318  
             }
 319  
             else
 320  
             {
 321  2236
                 key.append(node.getName(), true);
 322  
             }
 323  3039
             return key.toString();
 324  
         }
 325  
     }
 326  
 
 327  
     /**
 328  
      * <p>
 329  
      * Prepares Adding the property with the specified key.
 330  
      * </p>
 331  
      * <p>
 332  
      * To be able to deal with the structure supported by hierarchical
 333  
      * configuration implementations the passed in key is of importance,
 334  
      * especially the indices it might contain. The following example should
 335  
      * clarify this: Suppose the actual node structure looks like the
 336  
      * following:
 337  
      * </p>
 338  
      * <p>
 339  
      * <pre>
 340  
      *  tables
 341  
      *     +-- table
 342  
      *             +-- name = user
 343  
      *             +-- fields
 344  
      *                     +-- field
 345  
      *                             +-- name = uid
 346  
      *                     +-- field
 347  
      *                             +-- name = firstName
 348  
      *                     ...
 349  
      *     +-- table
 350  
      *             +-- name = documents
 351  
      *             +-- fields
 352  
      *                    ...
 353  
      * </pre>
 354  
      * </p>
 355  
      * <p>
 356  
      * In this example a database structure is defined, e.g. all fields of the
 357  
      * first table could be accessed using the key
 358  
      * {@code tables.table(0).fields.field.name}. If now properties are
 359  
      * to be added, it must be exactly specified at which position in the
 360  
      * hierarchy the new property is to be inserted. So to add a new field name
 361  
      * to a table it is not enough to say just
 362  
      * </p>
 363  
      * <p>
 364  
      * <pre>
 365  
      * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
 366  
      * </pre>
 367  
      * </p>
 368  
      * <p>
 369  
      * The statement given above contains some ambiguity. For instance it is not
 370  
      * clear, to which table the new field should be added. If this method finds
 371  
      * such an ambiguity, it is resolved by following the last valid path. Here
 372  
      * this would be the last table. The same is true for the {@code field};
 373  
      * because there are multiple fields and no explicit index is provided, a
 374  
      * new {@code name} property would be added to the last field - which
 375  
      * is probably not what was desired.
 376  
      * </p>
 377  
      * <p>
 378  
      * To make things clear explicit indices should be provided whenever
 379  
      * possible. In the example above the exact table could be specified by
 380  
      * providing an index for the {@code table} element as in
 381  
      * {@code tables.table(1).fields}. By specifying an index it can
 382  
      * also be expressed that at a given position in the configuration tree a
 383  
      * new branch should be added. In the example above we did not want to add
 384  
      * an additional {@code name} element to the last field of the table,
 385  
      * but we want a complete new {@code field} element. This can be
 386  
      * achieved by specifying an invalid index (like -1) after the element where
 387  
      * a new branch should be created. Given this our example would run:
 388  
      * </p>
 389  
      * <p>
 390  
      * <pre>
 391  
      * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
 392  
      * </pre>
 393  
      * </p>
 394  
      * <p>
 395  
      * With this notation it is possible to add new branches everywhere. We
 396  
      * could for instance create a new {@code table} element by
 397  
      * specifying
 398  
      * </p>
 399  
      * <p>
 400  
      * <pre>
 401  
      * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
 402  
      * </pre>
 403  
      * </p>
 404  
      * <p>
 405  
      * (Note that because after the {@code table} element a new branch is
 406  
      * created indices in following elements are not relevant; the branch is new
 407  
      * so there cannot be any ambiguities.)
 408  
      * </p>
 409  
      *
 410  
      * @param root the root node of the nodes hierarchy
 411  
      * @param key the key of the new property
 412  
      * @return a data object with information needed for the add operation
 413  
      */
 414  
     public NodeAddData prepareAdd(ConfigurationNode root, String key)
 415  
     {
 416  11032
         DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
 417  
                 this, key).iterator();
 418  11032
         if (!it.hasNext())
 419  
         {
 420  3
             throw new IllegalArgumentException(
 421  
                     "Key for add operation must be defined!");
 422  
         }
 423  
 
 424  11029
         NodeAddData result = new NodeAddData();
 425  11029
         result.setParent(findLastPathNode(it, root));
 426  
 
 427  14342
         while (it.hasNext())
 428  
         {
 429  3315
             if (!it.isPropertyKey())
 430  
             {
 431  1
                 throw new IllegalArgumentException(
 432  
                         "Invalid key for add operation: " + key
 433  
                                 + " (Attribute key in the middle.)");
 434  
             }
 435  3314
             result.addPathNode(it.currentKey());
 436  3314
             it.next();
 437  
         }
 438  
 
 439  11027
         result.setNewNodeName(it.currentKey());
 440  11027
         result.setAttribute(!it.isPropertyKey());
 441  11027
         return result;
 442  
     }
 443  
 
 444  
     /**
 445  
      * Recursive helper method for evaluating a key. This method processes all
 446  
      * facets of a configuration key, traverses the tree of properties and
 447  
      * fetches the the nodes of all matching properties.
 448  
      *
 449  
      * @param keyPart the configuration key iterator
 450  
      * @param node the actual node
 451  
      * @param nodes here the found nodes are stored
 452  
      */
 453  
     protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart,
 454  
             ConfigurationNode node, Collection<ConfigurationNode> nodes)
 455  
     {
 456  731807
         if (!keyPart.hasNext())
 457  
         {
 458  9576
             nodes.add(node);
 459  
         }
 460  
 
 461  
         else
 462  
         {
 463  722231
             String key = keyPart.nextKey(false);
 464  722227
             if (keyPart.isPropertyKey())
 465  
             {
 466  718273
                 processSubNodes(keyPart, node.getChildren(key), nodes);
 467  
             }
 468  722228
             if (keyPart.isAttribute())
 469  
             {
 470  3958
                 processSubNodes(keyPart, node.getAttributes(key), nodes);
 471  
             }
 472  
         }
 473  731805
     }
 474  
 
 475  
     /**
 476  
      * Finds the last existing node for an add operation. This method traverses
 477  
      * the configuration node tree along the specified key. The last existing
 478  
      * node on this path is returned.
 479  
      *
 480  
      * @param keyIt the key iterator
 481  
      * @param node the actual node
 482  
      * @return the last existing node on the given path
 483  
      */
 484  
     protected ConfigurationNode findLastPathNode(
 485  
             DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node)
 486  
     {
 487  19954
         String keyPart = keyIt.nextKey(false);
 488  
 
 489  19954
         if (keyIt.hasNext())
 490  
         {
 491  11555
             if (!keyIt.isPropertyKey())
 492  
             {
 493  
                 // Attribute keys can only appear as last elements of the path
 494  1
                 throw new IllegalArgumentException(
 495  
                         "Invalid path for add operation: "
 496  
                                 + "Attribute key in the middle!");
 497  
             }
 498  11554
             int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
 499  
                     .getChildrenCount(keyPart) - 1;
 500  11554
             if (idx < 0 || idx >= node.getChildrenCount(keyPart))
 501  
             {
 502  2629
                 return node;
 503  
             }
 504  
             else
 505  
             {
 506  8925
                 return findLastPathNode(keyIt, node.getChildren(keyPart).get(idx));
 507  
             }
 508  
         }
 509  
 
 510  
         else
 511  
         {
 512  8399
             return node;
 513  
         }
 514  
     }
 515  
 
 516  
     /**
 517  
      * Called by {@code findNodesForKey()} to process the sub nodes of
 518  
      * the current node depending on the type of the current key part (children,
 519  
      * attributes, or both).
 520  
      *
 521  
      * @param keyPart the key part
 522  
      * @param subNodes a list with the sub nodes to process
 523  
      * @param nodes the target collection
 524  
      */
 525  
     private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart,
 526  
             List<ConfigurationNode> subNodes, Collection<ConfigurationNode> nodes)
 527  
     {
 528  722231
         if (keyPart.hasIndex())
 529  
         {
 530  457
             if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
 531  
             {
 532  447
                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
 533  
                         .clone(), subNodes.get(keyPart.getIndex()), nodes);
 534  
             }
 535  
         }
 536  
         else
 537  
         {
 538  721775
             for (ConfigurationNode node : subNodes)
 539  
             {
 540  17788
                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
 541  
                         .clone(), node, nodes);
 542  17788
             }
 543  
         }
 544  722234
     }
 545  
 }