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}