    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;]</code>.</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 * @author <a
    112 * href="http://commons.apache.org/configuration/team-list.html">Commons
    113 * Configuration team</a>
    114 * @version $Id: DefaultExpressionEngine.java 1842194 2018-09-27 22:24:23Z ggregory $
    115 */
    116public class DefaultExpressionEngine implements ExpressionEngine
    117{
    118    /**
    119     * A default instance of this class that is used as expression engine for
    120     * hierarchical configurations per default.
    121     */
    122    public static final DefaultExpressionEngine INSTANCE =
    123            new DefaultExpressionEngine(
    124                    DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
    125
    126    /** The symbols used by this instance. */
    127    private final DefaultExpressionEngineSymbols symbols;
    128
    129    /** The matcher for node names. */
    130    private final NodeMatcher<String> nameMatcher;
    131
    132    /**
    133     * Creates a new instance of {@code DefaultExpressionEngine} and initializes
    134     * its symbols.
    135     *
    136     * @param syms the object with the symbols (must not be <b>null</b>)
    137     * @throws IllegalArgumentException if the symbols are <b>null</b>
    138     */
    139    public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms)
    140    {
    141        this(syms, null);
    142    }
    143
    144    /**
    145     * Creates a new instance of {@code DefaultExpressionEngine} and initializes
    146     * its symbols and the matcher for comparing node names. The passed in
    147     * matcher is always used when the names of nodes have to be matched against
    148     * parts of configuration keys.
    149     *
    150     * @param syms the object with the symbols (must not be <b>null</b>)
    151     * @param nodeNameMatcher the matcher for node names; can be <b>null</b>,
    152     *        then a default matcher is used
    153     * @throws IllegalArgumentException if the symbols are <b>null</b>
    154     */
    155    public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms,
    156            final NodeMatcher<String> nodeNameMatcher)
    157    {
    158        if (syms == null)
    159        {
    160            throw new IllegalArgumentException("Symbols must not be null!");
    161        }
    162
    163        symbols = syms;
    164        nameMatcher =
    165                (nodeNameMatcher != null) ? nodeNameMatcher
    166                        : NodeNameMatchers.EQUALS;
    167    }
    168
    169    /**
    170     * Returns the {@code DefaultExpressionEngineSymbols} object associated with
    171     * this instance.
    172     *
    173     * @return the {@code DefaultExpressionEngineSymbols} used by this engine
    174     * @since 2.0
    175     */
    176    public DefaultExpressionEngineSymbols getSymbols()
    177    {
    178        return symbols;
    179    }
    180
    181    /**
    182     * {@inheritDoc} This method supports the syntax as described in the class
    183     * comment.
    184     */
    185    @Override
    186    public <T> List<QueryResult<T>> query(final T root, final String key,
    187            final NodeHandler<T> handler)
    188    {
    189        final List<QueryResult<T>> results = new LinkedList<>();
    190        findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
    191                root, results, handler);
    192        return results;
    193    }
    194
    195    /**
    196     * {@inheritDoc} This implementation takes the
    197     * given parent key, adds a property delimiter, and then adds the node's
    198     * name.
    199     * The name of the root node is a blank string. Note that no indices are
    200     * returned.
    201     */
    202    @Override
    203    public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler)
    204    {
    205        if (parentKey == null)
    206        {
    207            // this is the root node
    208            return StringUtils.EMPTY;
    209        }
    210        final DefaultConfigurationKey key = new DefaultConfigurationKey(this,
    211                parentKey);
    212            key.append(handler.nodeName(node), true);
    213        return key.toString();
    214    }
    215
    216    @Override
    217    public String attributeKey(final String parentKey, final String attributeName)
    218    {
    219        final DefaultConfigurationKey key =
    220                new DefaultConfigurationKey(this, parentKey);
    221        key.appendAttribute(attributeName);
    222        return key.toString();
    223    }
    224
    225    /**
    226     * {@inheritDoc} This implementation works similar to {@code nodeKey()};
    227     * however, each key returned by this method has an index (except for the
    228     * root node). The parent key is prepended to the name of the current node
    229     * in any case and without further checks. If it is <b>null</b>, only the
    230     * name of the current node with its index is returned.
    231     */
    232    @Override
    233    public <T> String canonicalKey(final T node, final String parentKey,
    234            final NodeHandler<T> handler)
    235    {
    236        final String nodeName = handler.nodeName(node);
    237        final T parent = handler.getParent(node);
    238        final DefaultConfigurationKey key =
    239                new DefaultConfigurationKey(this, parentKey);
    240        key.append(StringUtils.defaultString(nodeName));
    241
    242        if (parent != null)
    243        {
    244            // this is not the root key
    245            key.appendIndex(determineIndex(node, parent, nodeName, handler));
    246        }
    247        return key.toString();
    248    }
    249
    250    /**
    251     * <p>
    252     * Prepares Adding the property with the specified key.
    253     * </p>
    254     * <p>
    255     * To be able to deal with the structure supported by hierarchical
    256     * configuration implementations the passed in key is of importance,
    257     * especially the indices it might contain. The following example should
    258     * clarify this: Suppose the current node structure looks like the
    259     * following:
    260     * </p>
    261     * <pre>
    262     *  tables
    263     *     +-- table
    264     *             +-- name = user
    265     *             +-- fields
    266     *                     +-- field
    267     *                             +-- name = uid
    268     *                     +-- field
    269     *                             +-- name = firstName
    270     *                     ...
    271     *     +-- table
    272     *             +-- name = documents
    273     *             +-- fields
    274     *                    ...
    275     * </pre>
    276     * <p>
    277     * In this example a database structure is defined, e.g. all fields of the
    278     * first table could be accessed using the key
    279     * {@code tables.table(0).fields.field.name}. If now properties are
    280     * to be added, it must be exactly specified at which position in the
    281     * hierarchy the new property is to be inserted. So to add a new field name
    282     * to a table it is not enough to say just
    283     * </p>
    284     * <pre>
    285     * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
    286     * </pre>
    287     * <p>
    288     * The statement given above contains some ambiguity. For instance it is not
    289     * clear, to which table the new field should be added. If this method finds
    290     * such an ambiguity, it is resolved by following the last valid path. Here
    291     * this would be the last table. The same is true for the {@code field};
    292     * because there are multiple fields and no explicit index is provided, a
    293     * new {@code name} property would be added to the last field - which
    294     * is probably not what was desired.
    295     * </p>
    296     * <p>
    297     * To make things clear explicit indices should be provided whenever
    298     * possible. In the example above the exact table could be specified by
    299     * providing an index for the {@code table} element as in
    300     * {@code tables.table(1).fields}. By specifying an index it can
    301     * also be expressed that at a given position in the configuration tree a
    302     * new branch should be added. In the example above we did not want to add
    303     * an additional {@code name} element to the last field of the table,
    304     * but we want a complete new {@code field} element. This can be
    305     * achieved by specifying an invalid index (like -1) after the element where
    306     * a new branch should be created. Given this our example would run:
    307     * </p>
    308     * <pre>
    309     * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
    310     * </pre>
    311     * <p>
    312     * With this notation it is possible to add new branches everywhere. We
    313     * could for instance create a new {@code table} element by
    314     * specifying
    315     * </p>
    316     * <pre>
    317     * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
    318     * </pre>
    319     * <p>
    320     * (Note that because after the {@code table} element a new branch is
    321     * created indices in following elements are not relevant; the branch is new
    322     * so there cannot be any ambiguities.)
    323     * </p>
    324     *
    325     * @param <T> the type of the nodes to be dealt with
    326     * @param root the root node of the nodes hierarchy
    327     * @param key the key of the new property
    328     * @param handler the node handler
    329     * @return a data object with information needed for the add operation
    330     */
    331    @Override
    332    public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler)
    333    {
    334        final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    335                this, key).iterator();
    336        if (!it.hasNext())
    337        {
    338            throw new IllegalArgumentException(
    339                    "Key for add operation must be defined!");
    340        }
    341
    342        final T parent = findLastPathNode(it, root, handler);
    343        final List<String> pathNodes = new LinkedList<>();
    344
    345        while (it.hasNext())
    346        {
    347            if (!it.isPropertyKey())
    348            {
    349                throw new IllegalArgumentException(
    350                        "Invalid key for add operation: " + key
    351                                + " (Attribute key in the middle.)");
    352            }
    353            pathNodes.add(it.currentKey());
    354            it.next();
    355        }
    356
    357        return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(),
    358                pathNodes);
    359    }
    360
    361    /**
    362     * Recursive helper method for evaluating a key. This method processes all
    363     * facets of a configuration key, traverses the tree of properties and
    364     * fetches the results of all matching properties.
    365     *
    366     * @param <T> the type of nodes to be dealt with
    367     * @param keyPart the configuration key iterator
    368     * @param node the current node
    369     * @param results here the found results are stored
    370     * @param handler the node handler
    371     */
    372    protected <T> void findNodesForKey(
    373            final DefaultConfigurationKey.KeyIterator keyPart, final T node,
    374            final Collection<QueryResult<T>> results, final NodeHandler<T> handler)
    375    {
    376        if (!keyPart.hasNext())
    377        {
    378            results.add(QueryResult.createNodeResult(node));
    379        }
    380
    381        else
    382        {
    383            final String key = keyPart.nextKey(false);
    384            if (keyPart.isPropertyKey())
    385            {
    386                processSubNodes(keyPart, findChildNodesByName(handler, node, key),
    387                        results, handler);
    388            }
    389            if (keyPart.isAttribute() && !keyPart.hasNext())
    390            {
    391                if (handler.getAttributeValue(node, key) != null)
    392                {
    393                    results.add(QueryResult.createAttributeResult(node, key));
    394                }
    395            }
    396        }
    397    }
    398
    399    /**
    400     * Finds the last existing node for an add operation. This method traverses
    401     * the node tree along the specified key. The last existing node on this
    402     * path is returned.
    403     *
    404     * @param <T> the type of the nodes to be dealt with
    405     * @param keyIt the key iterator
    406     * @param node the current node
    407     * @param handler the node handler
    408     * @return the last existing node on the given path
    409     */
    410    protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt,
    411            final T node, final NodeHandler<T> handler)
    412    {
    413        final String keyPart = keyIt.nextKey(false);
    414
    415        if (keyIt.hasNext())
    416        {
    417            if (!keyIt.isPropertyKey())
    418            {
    419                // Attribute keys can only appear as last elements of the path
    420                throw new IllegalArgumentException(
    421                        "Invalid path for add operation: "
    422                                + "Attribute key in the middle!");
    423            }
    424            final int idx =
    425                    keyIt.hasIndex() ? keyIt.getIndex() : handler
    426                            .getMatchingChildrenCount(node, nameMatcher,
    427                                    keyPart) - 1;
    428            if (idx < 0
    429                    || idx >= handler.getMatchingChildrenCount(node,
    430                            nameMatcher, keyPart))
    431            {
    432                return node;
    433            }
    434            return findLastPathNode(keyIt,
    435                    findChildNodesByName(handler, node, keyPart).get(idx),
    436                    handler);
    437        }
    438        return node;
    439    }
    440
    441    /**
    442     * Called by {@code findNodesForKey()} to process the sub nodes of
    443     * the current node depending on the type of the current key part (children,
    444     * attributes, or both).
    445     *
    446     * @param <T> the type of the nodes to be dealt with
    447     * @param keyPart the key part
    448     * @param subNodes a list with the sub nodes to process
    449     * @param nodes the target collection
    450     * @param handler the node handler
    451     */
    452    private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart,
    453            final List<T> subNodes, final Collection<QueryResult<T>> nodes, final NodeHandler<T> handler)
    454    {
    455        if (keyPart.hasIndex())
    456        {
    457            if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
    458            {
    459                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    460                        .clone(), subNodes.get(keyPart.getIndex()), nodes, handler);
    461            }
    462        }
    463        else
    464        {
    465            for (final T node : subNodes)
    466            {
    467                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
    468                        .clone(), node, nodes, handler);
    469            }
    470        }
    471    }
    472
    473    /**
    474     * Determines the index of the given node based on its parent node.
    475     *
    476     * @param node the current node
    477     * @param parent the parent node
    478     * @param nodeName the name of the current node
    479     * @param handler the node handler
    480     * @param <T> the type of the nodes to be dealt with
    481     * @return the index of this node
    482     */
    483    private <T> int determineIndex(final T node, final T parent, final String nodeName,
    484                                          final NodeHandler<T> handler)
    485    {
    486        return findChildNodesByName(handler, parent, nodeName).indexOf(node);
    487    }
    488
    489    /**
    490     * Returns a list with all child nodes of the given parent node which match
    491     * the specified node name. The match is done using the current node name
    492     * matcher.
    493     *
    494     * @param handler the {@code NodeHandler}
    495     * @param parent the parent node
    496     * @param nodeName the name of the current node
    497     * @param <T> the type of the nodes to be dealt with
    498     * @return a list with all matching child nodes
    499     */
    500    private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent,
    501            final String nodeName)
    502    {
    503        return handler.getMatchingChildren(parent, nameMatcher, nodeName);
    504    }
    505}