    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 java.util.Collection;
    020    import java.util.Iterator;
    021    import java.util.LinkedList;
    022    import java.util.List;
    023
    024    import org.apache.commons.lang.StringUtils;
    025
    026    /**
    027     * <p>
    028     * A default implementation of the <code>ExpressionEngine</code> interface
    029     * providing the &quot;native&quote; expression language for hierarchical
    030     * configurations.
    031     * </p>
    032     * <p>
    033     * This class implements a rather simple expression language for navigating
    034     * through a hierarchy of configuration nodes. It supports the following
    035     * operations:
    036     * </p>
    037     * <p>
    038     * <ul>
    039     * <li>Navigating from a node to one of its children using the child node
    040     * delimiter, which is by the default a dot (&quot;.&quot;).</li>
    041     * <li>Navigating from a node to one of its attributes using the attribute node
    042     * delimiter, which by default follows the XPATH like syntax
    043     * <code>[@&lt;attributeName&gt;]</code>.</li>
    044     * <li>If there are multiple child or attribute nodes with the same name, a
    045     * specific node can be selected using a numerical index. By default indices are
    046     * written in paranthesis.</li>
    047     * </ul>
    048     * </p>
    049     * <p>
    050     * As an example consider the following XML document:
    051     * </p>
    052     *
    053     * <pre>
    054     *  &lt;database&gt;
    055     *    &lt;tables&gt;
    056     *      &lt;table type=&quot;system&quot;&gt;
    057     *        &lt;name&gt;users&lt;/name&gt;
    058     *        &lt;fields&gt;
    059     *          &lt;field&gt;
    060     *            &lt;name&gt;lid&lt;/name&gt;
    061     *            &lt;type&gt;long&lt;/name&gt;
    062     *          &lt;/field&gt;
    063     *          &lt;field&gt;
    064     *            &lt;name&gt;usrName&lt;/name&gt;
    065     *            &lt;type&gt;java.lang.String&lt;/type&gt;
    066     *          &lt;/field&gt;
    067     *         ...
    068     *        &lt;/fields&gt;
    069     *      &lt;/table&gt;
    070     *      &lt;table&gt;
    071     *        &lt;name&gt;documents&lt;/name&gt;
    072     *        &lt;fields&gt;
    073     *          &lt;field&gt;
    074     *            &lt;name&gt;docid&lt;/name&gt;
    075     *            &lt;type&gt;long&lt;/type&gt;
    076     *          &lt;/field&gt;
    077     *          ...
    078     *        &lt;/fields&gt;
    079     *      &lt;/table&gt;
    080     *      ...
    081     *    &lt;/tables&gt;
    082     *  &lt;/database&gt;
    083     * </pre>
    084     *
    085     * </p>
    086     * <p>
    087     * If this document is parsed and stored in a hierarchical configuration object,
    088     * for instance the key <code>tables.table(0).name</code> can be used to find
    089     * out the name of the first table. In opposite <code>tables.table.name</code>
    090     * would return a collection with the names of all available tables. Similarily
    091     * the key <code>tables.table(1).fields.field.name</code> returns a collection
    092     * with the names of all fields of the second table. If another index is added
    093     * after the <code>field</code> element, a single field can be accessed:
    094     * <code>tables.table(1).fields.field(0).name</code>. The key
    095     * <code>tables.table(0)[@type]</code> would select the type attribute of the
    096     * first table.
    097     * </p>
    098     * <p>
    099     * 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    }