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