    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 */
    017package org.apache.commons.configuration2.tree;
    018
    019import java.util.Collection;
    020import java.util.LinkedList;
    021import java.util.List;
    022
    023import org.apache.commons.lang3.StringUtils;
    024
    025/**
    026 * <p>
    027 * A default implementation of the {@code ExpressionEngine} interface
    028 * providing the &quot;native&quot; expression language for hierarchical
    029 * configurations.
    030 * </p>
    031 * <p>
    032 * This class implements a rather simple expression language for navigating
    033 * through a hierarchy of configuration nodes. It supports the following
    034 * operations:
    035 * </p>
    036 * <ul>
    037 * <li>Navigating from a node to one of its children using the child node
    038 * delimiter, which is by the default a dot (&quot;.&quot;).</li>
    039 * <li>Navigating from a node to one of its attributes using the attribute node
    040 * delimiter, which by default follows the XPATH like syntax
    041 * {@code [@&lt;attributeName&gt;]}.</li>
    042 * <li>If there are multiple child or attribute nodes with the same name, a
    043 * specific node can be selected using a numerical index. By default indices are
    044 * written in parenthesis.</li>
    045 * </ul>
    046 * <p>
    047 * As an example consider the following XML document:
    048 * </p>
    049 *
    050 * <pre>
    051 *  &lt;database&gt;
    052 *    &lt;tables&gt;
    053 *      &lt;table type=&quot;system&quot;&gt;
    054 *        &lt;name&gt;users&lt;/name&gt;
    055 *        &lt;fields&gt;
    056 *          &lt;field&gt;
    057 *            &lt;name&gt;lid&lt;/name&gt;
    058 *            &lt;type&gt;long&lt;/name&gt;
    059 *          &lt;/field&gt;
    060 *          &lt;field&gt;
    061 *            &lt;name&gt;usrName&lt;/name&gt;
    062 *            &lt;type&gt;java.lang.String&lt;/type&gt;
    063 *          &lt;/field&gt;
    064 *         ...
    065 *        &lt;/fields&gt;
    066 *      &lt;/table&gt;
    067 *      &lt;table&gt;
    068 *        &lt;name&gt;documents&lt;/name&gt;
    069 *        &lt;fields&gt;
    070 *          &lt;field&gt;
    071 *            &lt;name&gt;docid&lt;/name&gt;
    072 *            &lt;type&gt;long&lt;/type&gt;
    073 *          &lt;/field&gt;
    074 *          ...
    075 *        &lt;/fields&gt;
    076 *      &lt;/table&gt;
    077 *      ...
    078 *    &lt;/tables&gt;
    079 *  &lt;/database&gt;
    080 * </pre>
    081 *
    082 * <p>
    083 * If this document is parsed and stored in a hierarchical configuration object,
    084 * for instance the key {@code tables.table(0).name} can be used to find
    085 * out the name of the first table. In opposite {@code tables.table.name}
    086 * would return a collection with the names of all available tables. Similarly
    087 * the key {@code tables.table(1).fields.field.name} returns a collection
    088 * with the names of all fields of the second table. If another index is added
    089 * after the {@code field} element, a single field can be accessed:
    090 * {@code tables.table(1).fields.field(0).name}. The key
    091 * {@code tables.table(0)[@type]} would select the type attribute of the
    092 * first table.
    093 * </p>
    094 * <p>
    095 * This example works with the default values for delimiters and index markers.
    096 * It is also possible to set custom values for these properties so that you can
    097 * adapt a {@code DefaultExpressionEngine} to your personal needs.
    098 * </p>
    099 * <p>
    100 * The concrete symbols used by an instance are determined by a
    101 * {@link DefaultExpressionEngineSymbols} object passed to the constructor.
    102 * By providing a custom symbols object the syntax for querying properties in
    103 * a hierarchical configuration can be altered.
    104 * </p>
    105 * <p>
    106 * Instances of this class are thread-safe and can be shared between multiple
    107 * hierarchical configuration objects.
    108 * </p>
    109 *
    110 * @since 1.3
    111 */
    112public class DefaultExpressionEngine implements ExpressionEngine
    113{
    114    /**
    115     * A default instance of this class that is used as expression engine for
    116     * hierarchical configurations per default.
    117     */
    118    public static final DefaultExpressionEngine INSTANCE =
    119            new DefaultExpressionEngine(
    120                    DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
    121
    122    /** The symbols used by this instance. */
    123    private final DefaultExpressionEngineSymbols symbols;
    124
    125    /** The matcher for node names. */
    126    private final NodeMatcher<String> nameMatcher;
    127
    128    /**
    129     * Creates a new instance of {@code DefaultExpressionEngine} and initializes
    130     * its symbols.
    131     *
    132     * @param syms the object with the symbols (must not be <b>null</b>)
    133     * @throws IllegalArgumentException if the symbols are <b>null</b>
    134     */
    135    public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms)
    136    {
    137        this(syms, null);
    138    }
    139
    140    /**
    141     * Creates a new instance of {@code DefaultExpressionEngine} and initializes
    142     * its symbols and the matcher for comparing node names. The passed in
    143     * matcher is always used when the names of nodes have to be matched against
    144     * parts of configuration keys.
    145     *
    146     * @param syms the object with the symbols (must not be <b>null</b>)
    147     * @param nodeNameMatcher the matcher for node names; can be <b>null</b>,
    148     *        then a default matcher is used
    149     * @throws IllegalArgumentException if the symbols are <b>null</b>
    150     */
    151    public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms,
    152            final NodeMatcher<String> nodeNameMatcher)
    153    {
    154        if (syms == null)
    155        {
    156            throw new IllegalArgumentException("Symbols must not be null!");
    157        }
    158
    159        symbols = syms;
    160        nameMatcher =
    161                nodeNameMatcher != null ? nodeNameMatcher
    162                        : NodeNameMatchers.EQUALS;
    163    }
    164
    165    /**
    166     * Returns the {@code DefaultExpressionEngineSymbols} object associated with
    167     * this instance.
    168     *
    169     * @return the {@code DefaultExpressionEngineSymbols} used by this engine
    170     * @since 2.0
    171     */
    172    public DefaultExpressionEngineSymbols getSymbols()
    173    {
    174        return symbols;
    175    }
    176
    177    /**
    178     * {@inheritDoc} This method supports the syntax as described in the class
    179     * comment.
    180     */
    181    @Override
    182    public <T> List<QueryResult<T>> query(final T root, final String key,
    183            final NodeHandler<T> handler)
    184    {
    185        final List<QueryResult<T>> results = new LinkedList<>();
    186        findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
    187                root, results, handler);
    188        return results;
    189    }
    190
    191    /**
    192     * {@inheritDoc} This implementation takes the
    193     * given parent key, adds a property delimiter, and then adds the node's
    194     * name.
    195     * The name of the root node is a blank string. Note that no indices are
    196     * returned.
    197     */
    198    @Override
    199    public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler)
    200    {
    201        if (parentKey == null)
    202        {
    203            // this is the root node
    204            return StringUtils.EMPTY;
    205        }
    206        final DefaultConfigurationKey key = new DefaultConfigurationKey(this,
    207                parentKey);
    208            key.append(handler.nodeName(node), true);
    209        return key.toString();
    210    }
    211
    212    @Override
    213    public String attributeKey(final String parentKey, final String attributeName)
    214    {
    215        final DefaultConfigurationKey key =
    216                new DefaultConfigurationKey(this, parentKey);
    217        key.appendAttribute(attributeName);
    218        return key.toString();
    219    }
    220
    221    /**
    222     * {@inheritDoc} This implementation works similar to {@code nodeKey()};
    223     * however, each key returned by this method has an index (except for the
    224     * root node). The parent key is prepended to the name of the current node
    225     * in any case and without further checks. If it is <b>null</b>, only the
    226     * name of the current node with its index is returned.
    227     */
    228    @Override
    229    public <T> String canonicalKey(final T node, final String parentKey,
    230            final NodeHandler<T> handler)
    231    {
    232        final String nodeName = handler.nodeName(node);
    233        final T parent = handler.getParent(node);
    234        final DefaultConfigurationKey key =
    235                new DefaultConfigurationKey(this, parentKey);
    236        key.append(StringUtils.defaultString(nodeName));
    237
    238        if (parent != null)
    239        {
    240            // this is not the root key
    241            key.appendIndex(determineIndex(node, parent, nodeName, handler));
    242        }
    243        return key.toString();
    244    }
    245
    246    /**
    247     * <p>
    248     * Prepares Adding the property with the specified key.
    249     * </p>
    250     * <p>
    251     * To be able to deal with the structure supported by hierarchical
    252     * configuration implementations the passed in key is of importance,
    253     * especially the indices it might contain. The following example should
    254     * clarify this: Suppose the current node structure looks like the
    255     * following:
    256     * </p>
    257     * <pre>
    258     *  tables
    259     *     +-- table
    260     *             +-- name = user
    261     *             +-- fields
    262     *                     +-- field
    263     *                             +-- name = uid
    264     *                     +-- field
    265     *                             +-- name = firstName
    266     *                     ...
    267     *     +-- table
    268     *             +-- name = documents
    269     *             +-- fields
    270     *                    ...
    271     * </pre>
    272     * <p>
    273     * In this example a database structure is defined, e.g. all fields of the
    274     * first table could be accessed using the key
    275     * {@code tables.table(0).fields.field.name}. If now properties are
    276     * to be added, it must be exactly specified at which position in the
    277     * hierarchy the new property is to be inserted. So to add a new field name
    278     * to a table it is not enough to say just
    279     * </p>
    280     * <pre>
    281     * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
    282     * </pre>
    283     * <p>
    284     * The statement given above contains some ambiguity. For instance it is not
    285     * clear, to which table the new field should be added. If this method finds
    286     * such an ambiguity, it is resolved by following the last valid path. Here
    287     * this would be the last table. The same is true for the {@code field};
    288     * because there are multiple fields and no explicit index is provided, a
    289     * new {@code name} property would be added to the last field - which
    290     * is probably not what was desired.
    291     * </p>
    292     * <p>
    293     * To make things clear explicit indices should be provided whenever
    294     * possible. In the example above the exact table could be specified by
    295     * providing an index for the {@code table} element as in
    296     * {@code tables.table(1).fields}. By specifying an index it can
    297     * also be expressed that at a given position in the configuration tree a
    298     * new branch should be added. In the example above we did not want to add
    299     * an additional {@code name} element to the last field of the table,
    300     * but we want a complete new {@code field} element. This can be
    301     * achieved by specifying an invalid index (like -1) after the element where
    302     * a new branch should be created. Given this our example would run:
    303     * </p>
    304     * <pre>
    305     * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
    306     * </pre>
    307     * <p>
    308     * With this notation it is possible to add new branches everywhere. We
    309     * could for instance create a new {@code table} element by
    310     * specifying
    311     * </p>
    312     * <pre>
    313     * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
    314     * </pre>
    315     * <p>
    316     * (Note that because after the {@code table} element a new branch is
    317     * created indices in following elements are not relevant; the branch is new
    318     * so there cannot be any ambiguities.)
    319     * </p>
    320     *
    321     * @param <T> the type of the nodes to be dealt with
    322     * @param root the root node of the nodes hierarchy
    323     * @param key the key of the new property
    324     * @param handler the node handler
    325     * @return a data object with information needed for the add operation
    326     */
    327    @Override
    328    public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler)
    329    {
    330        final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    331                this, key).iterator();
    332        if (!it.hasNext())
    333        {
    334            throw new IllegalArgumentException(
    335                    "Key for add operation must be defined!");
    336        }
    337
    338        final T parent = findLastPathNode(it, root, handler);
    339        final List<String> pathNodes = new LinkedList<>();
    340
    341        while (it.hasNext())
    342        {
    343            if (!it.isPropertyKey())
    344            {
    345                throw new IllegalArgumentException(
    346                        "Invalid key for add operation: " + key
    347                                + " (Attribute key in the middle.)");
    348            }
    349            pathNodes.add(it.currentKey());
    350            it.next();
    351        }
    352
    353        return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(),
    354                pathNodes);
    355    }
    356
    357    /**
    358     * Recursive helper method for evaluating a key. This method processes all
    359     * facets of a configuration key, traverses the tree of properties and
    360     * fetches the results of all matching properties.
    361     *
    362     * @param <T> the type of nodes to be dealt with
    363     * @param keyPart the configuration key iterator
    364     * @param node the current node
    365     * @param results here the found results are stored
    366     * @param handler the node handler
    367     */
    368    protected <T> void findNodesForKey(
    369            final DefaultConfigurationKey.KeyIterator keyPart, final T node,
    370            final Collection<QueryResult<T>> results, final NodeHandler<T> handler)
    371    {
    372        if (!keyPart.hasNext())
    373        {
    374            results.add(QueryResult.createNodeResult(node));
    375        }
    376
    377        else
    378        {
    379            final String key = keyPart.nextKey(false);
    380            if (keyPart.isPropertyKey())
    381            {
    382                processSubNodes(keyPart, findChildNodesByName(handler, node, key),
    383                        results, handler);
    384            }
    385            if (keyPart.isAttribute() && !keyPart.hasNext())
    386            {
    387                if (handler.getAttributeValue(node, key) != null)
    388                {
    389                    results.add(QueryResult.createAttributeResult(node, key));
    390                }
    391            }
    392        }
    393    }
    394
    395    /**
    396     * Finds the last existing node for an add operation. This method traverses
    397     * the node tree along the specified key. The last existing node on this
    398     * path is returned.
    399     *
    400     * @param <T> the type of the nodes to be dealt with
    401     * @param keyIt the key iterator
    402     * @param node the current node
    403     * @param handler the node handler
    404     * @return the last existing node on the given path
    405     */
    406    protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt,
    407            final T node, final NodeHandler<T> handler)
    408    {
    409        final String keyPart = keyIt.nextKey(false);
    410
    411        if (keyIt.hasNext())
    412        {
    413            if (!keyIt.isPropertyKey())
    414            {
    415                // Attribute keys can only appear as last elements of the path
    416                throw new IllegalArgumentException(
    417                        "Invalid path for add operation: "
    418                                + "Attribute key in the middle!");
    419            }
    420            final int idx =
    421                    keyIt.hasIndex() ? keyIt.getIndex() : handler
    422                            .getMatchingChildrenCount(node, nameMatcher,
    423                                    keyPart) - 1;
    424            if (idx < 0
    425                    || idx >= handler.getMatchingChildrenCount(node,
    426                            nameMatcher, keyPart))
    427            {
    428                return node;
    429            }
    430            return findLastPathNode(keyIt,
    431                    findChildNodesByName(handler, node, keyPart).get(idx),
    432                    handler);
    433        }
    434        return node;
    435    }
    436
    437    /**
    438     * Called by {@code findNodesForKey()} to process the sub nodes of
    439     * the current node depending on the type of the current key part (children,
    440     * attributes, or both).
    441     *
    442     * @param <T> the type of the nodes to be dealt with
    443     * @param keyPart the key part
    444     * @param subNodes a list with the sub nodes to process
    445     * @param nodes the target collection
    446     * @param handler the node handler
    447     */
    448    private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart,
    449            final List<T> subNodes, final Collection<QueryResult<T>> nodes, final NodeHandler<T> handler)
    450    {
    451        if (keyPart.hasIndex())
    452        {
    453            if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
    454            {
    455                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    456                        .clone(), subNodes.get(keyPart.getIndex()), nodes, handler);
    457            }
    458        }
    459        else
    460        {
    461            for (final T node : subNodes)
    462            {
    463                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    464                        .clone(), node, nodes, handler);
    465            }
    466        }
    467    }
    468
    469    /**
    470     * Determines the index of the given node based on its parent node.
    471     *
    472     * @param node the current node
    473     * @param parent the parent node
    474     * @param nodeName the name of the current node
    475     * @param handler the node handler
    476     * @param <T> the type of the nodes to be dealt with
    477     * @return the index of this node
    478     */
    479    private <T> int determineIndex(final T node, final T parent, final String nodeName,
    480                                          final NodeHandler<T> handler)
    481    {
    482        return findChildNodesByName(handler, parent, nodeName).indexOf(node);
    483    }
    484
    485    /**
    486     * Returns a list with all child nodes of the given parent node which match
    487     * the specified node name. The match is done using the current node name
    488     * matcher.
    489     *
    490     * @param handler the {@code NodeHandler}
    491     * @param parent the parent node
    492     * @param nodeName the name of the current node
    493     * @param <T> the type of the nodes to be dealt with
    494     * @return a list with all matching child nodes
    495     */
    496    private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent,
    497            final String nodeName)
    498    {
    499        return handler.getMatchingChildren(parent, nameMatcher, nodeName);
    500    }
    501}