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