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