View Javadoc

    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 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     private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
    131
    132     /*** Stores the escaped property delimiter. */
    133     private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
    134
    135     /*** Stores the attribute start marker. */
    136     private String attributeStart = DEFAULT_ATTRIBUTE_START;
    137
    138     /*** Stores the attribute end marker. */
    139     private String attributeEnd = DEFAULT_ATTRIBUTE_END;
    140
    141     /*** Stores the index start marker. */
    142     private String indexStart = DEFAULT_INDEX_START;
    143
    144     /*** stores the index end marker. */
    145     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         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         this.attributeEnd = attributeEnd;
    166     }
    167
    168     /***
    169      * Returns the attribute start marker.
    170      *
    171      * @return the attribute start marker
    172      */
    173     public String getAttributeStart()
    174     {
    175         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         this.attributeStart = attributeStart;
    187     }
    188
    189     /***
    190      * Returns the escaped property delimiter string.
    191      *
    192      * @return the escaped property delimiter
    193      */
    194     public String getEscapedDelimiter()
    195     {
    196         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         this.escapedDelimiter = escapedDelimiter;
    211     }
    212
    213     /***
    214      * Returns the index end marker.
    215      *
    216      * @return the index end marker
    217      */
    218     public String getIndexEnd()
    219     {
    220         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         this.indexEnd = indexEnd;
    231     }
    232
    233     /***
    234      * Returns the index start marker.
    235      *
    236      * @return the index start marker
    237      */
    238     public String getIndexStart()
    239     {
    240         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         this.indexStart = indexStart;
    252     }
    253
    254     /***
    255      * Returns the property delimiter.
    256      *
    257      * @return the property delimiter
    258      */
    259     public String getPropertyDelimiter()
    260     {
    261         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         this.propertyDelimiter = propertyDelimiter;
    273     }
    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         List nodes = new LinkedList();
    286         findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
    287                 root, nodes);
    288         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         if (parentKey == null)
    305         {
    306             // this is the root node
    307             return StringUtils.EMPTY;
    308         }
    309
    310         else
    311         {
    312             DefaultConfigurationKey key = new DefaultConfigurationKey(this,
    313                     parentKey);
    314             if (node.isAttribute())
    315             {
    316                 key.appendAttribute(node.getName());
    317             }
    318             else
    319             {
    320                 key.append(node.getName(), true);
    321             }
    322             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         DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    416                 this, key).iterator();
    417         if (!it.hasNext())
    418         {
    419             throw new IllegalArgumentException(
    420                     "Key for add operation must be defined!");
    421         }
    422
    423         NodeAddData result = new NodeAddData();
    424         result.setParent(findLastPathNode(it, root));
    425
    426         while (it.hasNext())
    427         {
    428             if (!it.isPropertyKey())
    429             {
    430                 throw new IllegalArgumentException(
    431                         "Invalid key for add operation: " + key
    432                                 + " (Attribute key in the middle.)");
    433             }
    434             result.addPathNode(it.currentKey());
    435             it.next();
    436         }
    437
    438         result.setNewNodeName(it.currentKey());
    439         result.setAttribute(!it.isPropertyKey());
    440         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         if (!keyPart.hasNext())
    456         {
    457             nodes.add(node);
    458         }
    459
    460         else
    461         {
    462             String key = keyPart.nextKey(false);
    463             if (keyPart.isPropertyKey())
    464             {
    465                 processSubNodes(keyPart, node.getChildren(key), nodes);
    466             }
    467             if (keyPart.isAttribute())
    468             {
    469                 processSubNodes(keyPart, node.getAttributes(key), nodes);
    470             }
    471         }
    472     }
    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         String keyPart = keyIt.nextKey(false);
    487
    488         if (keyIt.hasNext())
    489         {
    490             if (!keyIt.isPropertyKey())
    491             {
    492                 // Attribute keys can only appear as last elements of the path
    493                 throw new IllegalArgumentException(
    494                         "Invalid path for add operation: "
    495                                 + "Attribute key in the middle!");
    496             }
    497             int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
    498                     .getChildrenCount(keyPart) - 1;
    499             if (idx < 0 || idx >= node.getChildrenCount(keyPart))
    500             {
    501                 return node;
    502             }
    503             else
    504             {
    505                 return findLastPathNode(keyIt, (ConfigurationNode) node
    506                         .getChildren(keyPart).get(idx));
    507             }
    508         }
    509
    510         else
    511         {
    512             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         if (keyPart.hasIndex())
    529         {
    530             if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
    531             {
    532                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    533                         .clone(), (ConfigurationNode) subNodes.get(keyPart
    534                         .getIndex()), nodes);
    535             }
    536         }
    537         else
    538         {
    539             for (Iterator it = subNodes.iterator(); it.hasNext();)
    540             {
    541                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    542                         .clone(), (ConfigurationNode) it.next(), nodes);
    543             }
    544         }
    545     }
    546 }