View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.tree;
18  
19  import java.util.Collection;
20  import java.util.LinkedList;
21  import java.util.List;
22  
23  import org.apache.commons.lang3.StringUtils;
24  
25  /**
26   * <p>
27   * A default implementation of the {@code ExpressionEngine} interface providing the &quot;native&quot; expression
28   * language for hierarchical configurations.
29   * </p>
30   * <p>
31   * This class implements a rather simple expression language for navigating through a hierarchy of configuration nodes.
32   * It supports the following operations:
33   * </p>
34   * <ul>
35   * <li>Navigating from a node to one of its children using the child node delimiter, which is by the default a dot
36   * (&quot;.&quot;).</li>
37   * <li>Navigating from a node to one of its attributes using the attribute node delimiter, which by default follows the
38   * XPATH like syntax {@code [@&lt;attributeName&gt;]}.</li>
39   * <li>If there are multiple child or attribute nodes with the same name, a specific node can be selected using a
40   * numerical index. By default indices are written in parenthesis.</li>
41   * </ul>
42   * <p>
43   * As an example consider the following XML document:
44   * </p>
45   *
46   * <pre>
47   *  &lt;database&gt;
48   *    &lt;tables&gt;
49   *      &lt;table type=&quot;system&quot;&gt;
50   *        &lt;name&gt;users&lt;/name&gt;
51   *        &lt;fields&gt;
52   *          &lt;field&gt;
53   *            &lt;name&gt;lid&lt;/name&gt;
54   *            &lt;type&gt;long&lt;/name&gt;
55   *          &lt;/field&gt;
56   *          &lt;field&gt;
57   *            &lt;name&gt;usrName&lt;/name&gt;
58   *            &lt;type&gt;java.lang.String&lt;/type&gt;
59   *          &lt;/field&gt;
60   *         ...
61   *        &lt;/fields&gt;
62   *      &lt;/table&gt;
63   *      &lt;table&gt;
64   *        &lt;name&gt;documents&lt;/name&gt;
65   *        &lt;fields&gt;
66   *          &lt;field&gt;
67   *            &lt;name&gt;docid&lt;/name&gt;
68   *            &lt;type&gt;long&lt;/type&gt;
69   *          &lt;/field&gt;
70   *          ...
71   *        &lt;/fields&gt;
72   *      &lt;/table&gt;
73   *      ...
74   *    &lt;/tables&gt;
75   *  &lt;/database&gt;
76   * </pre>
77   *
78   * <p>
79   * If this document is parsed and stored in a hierarchical configuration object, for instance the key
80   * {@code tables.table(0).name} can be used to find out the name of the first table. In opposite
81   * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key
82   * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If
83   * another index is added after the {@code field} element, a single field can be accessed:
84   * {@code tables.table(1).fields.field(0).name}. The key {@code tables.table(0)[@type]} would select the type attribute
85   * of the first table.
86   * </p>
87   * <p>
88   * This example works with the default values for delimiters and index markers. It is also possible to set custom values
89   * for these properties so that you can adapt a {@code DefaultExpressionEngine} to your personal needs.
90   * </p>
91   * <p>
92   * The concrete symbols used by an instance are determined by a {@link DefaultExpressionEngineSymbols} object passed to
93   * the constructor. By providing a custom symbols object the syntax for querying properties in a hierarchical
94   * configuration can be altered.
95   * </p>
96   * <p>
97   * Instances of this class are thread-safe and can be shared between multiple hierarchical configuration objects.
98   * </p>
99   *
100  * @since 1.3
101  */
102 public class DefaultExpressionEngine implements ExpressionEngine {
103 
104     /**
105      * A default instance of this class that is used as expression engine for hierarchical configurations per default.
106      */
107     public static final DefaultExpressionEngine INSTANCE = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
108 
109     /** The symbols used by this instance. */
110     private final DefaultExpressionEngineSymbols symbols;
111 
112     /** The matcher for node names. */
113     private final NodeMatcher<String> nameMatcher;
114 
115     /**
116      * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols.
117      *
118      * @param syms the object with the symbols (must not be <strong>null</strong>)
119      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
120      */
121     public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms) {
122         this(syms, null);
123     }
124 
125     /**
126      * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols and the matcher for comparing
127      * node names. The passed in matcher is always used when the names of nodes have to be matched against parts of
128      * configuration keys.
129      *
130      * @param syms the object with the symbols (must not be <strong>null</strong>)
131      * @param nodeNameMatcher the matcher for node names; can be <strong>null</strong>, then a default matcher is used
132      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
133      */
134     public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms, final NodeMatcher<String> nodeNameMatcher) {
135         if (syms == null) {
136             throw new IllegalArgumentException("Symbols must not be null.");
137         }
138 
139         symbols = syms;
140         nameMatcher = nodeNameMatcher != null ? nodeNameMatcher : NodeNameMatchers.EQUALS;
141     }
142 
143     @Override
144     public String attributeKey(final String parentKey, final String attributeName) {
145         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
146         key.appendAttribute(attributeName);
147         return key.toString();
148     }
149 
150     /**
151      * {@inheritDoc} This implementation works similar to {@code nodeKey()}; however, each key returned by this method has
152      * an index (except for the root node). The parent key is prepended to the name of the current node in any case and
153      * without further checks. If it is <strong>null</strong>, only the name of the current node with its index is returned.
154      */
155     @Override
156     public <T> String canonicalKey(final T node, final String parentKey, final NodeHandler<T> handler) {
157         final String nodeName = handler.nodeName(node);
158         final T parent = handler.getParent(node);
159         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
160         key.append(StringUtils.defaultString(nodeName));
161 
162         if (parent != null) {
163             // this is not the root key
164             key.appendIndex(determineIndex(node, parent, nodeName, handler));
165         }
166         return key.toString();
167     }
168 
169     /**
170      * Determines the index of the given node based on its parent node.
171      *
172      * @param node the current node
173      * @param parent the parent node
174      * @param nodeName the name of the current node
175      * @param handler the node handler
176      * @param <T> the type of the nodes to be dealt with
177      * @return the index of this node
178      */
179     private <T> int determineIndex(final T node, final T parent, final String nodeName, final NodeHandler<T> handler) {
180         return findChildNodesByName(handler, parent, nodeName).indexOf(node);
181     }
182 
183     /**
184      * Returns a list with all child nodes of the given parent node which match the specified node name. The match is done
185      * using the current node name matcher.
186      *
187      * @param handler the {@code NodeHandler}
188      * @param parent the parent node
189      * @param nodeName the name of the current node
190      * @param <T> the type of the nodes to be dealt with
191      * @return a list with all matching child nodes
192      */
193     private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent, final String nodeName) {
194         return handler.getMatchingChildren(parent, nameMatcher, nodeName);
195     }
196 
197     /**
198      * Finds the last existing node for an add operation. This method traverses the node tree along the specified key. The
199      * last existing node on this path is returned.
200      *
201      * @param <T> the type of the nodes to be dealt with
202      * @param keyIt the key iterator
203      * @param node the current node
204      * @param handler the node handler
205      * @return the last existing node on the given path
206      */
207     protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt, final T node, final NodeHandler<T> handler) {
208         final String keyPart = keyIt.nextKey(false);
209 
210         if (keyIt.hasNext()) {
211             if (!keyIt.isPropertyKey()) {
212                 // Attribute keys can only appear as last elements of the path
213                 throw new IllegalArgumentException("Invalid path for add operation: Attribute key in the middle.");
214             }
215             final int idx = keyIt.hasIndex() ? keyIt.getIndex() : handler.getMatchingChildrenCount(node, nameMatcher, keyPart) - 1;
216             if (idx < 0 || idx >= handler.getMatchingChildrenCount(node, nameMatcher, keyPart)) {
217                 return node;
218             }
219             return findLastPathNode(keyIt, findChildNodesByName(handler, node, keyPart).get(idx), handler);
220         }
221         return node;
222     }
223 
224     /**
225      * Recursive helper method for evaluating a key. This method processes all facets of a configuration key, traverses the
226      * tree of properties and fetches the results of all matching properties.
227      *
228      * @param <T> the type of nodes to be dealt with
229      * @param keyPart the configuration key iterator
230      * @param node the current node
231      * @param results here the found results are stored
232      * @param handler the node handler
233      */
234     protected <T> void findNodesForKey(final DefaultConfigurationKey.KeyIterator keyPart, final T node, final Collection<QueryResult<T>> results,
235         final NodeHandler<T> handler) {
236         if (!keyPart.hasNext()) {
237             results.add(QueryResult.createNodeResult(node));
238         } else {
239             final String key = keyPart.nextKey(false);
240             if (keyPart.isPropertyKey()) {
241                 processSubNodes(keyPart, findChildNodesByName(handler, node, key), results, handler);
242             }
243             if (keyPart.isAttribute() && !keyPart.hasNext() && handler.getAttributeValue(node, key) != null) {
244                 results.add(QueryResult.createAttributeResult(node, key));
245             }
246         }
247     }
248 
249     /**
250      * Gets the {@code DefaultExpressionEngineSymbols} object associated with this instance.
251      *
252      * @return the {@code DefaultExpressionEngineSymbols} used by this engine
253      * @since 2.0
254      */
255     public DefaultExpressionEngineSymbols getSymbols() {
256         return symbols;
257     }
258 
259     /**
260      * {@inheritDoc} This implementation takes the given parent key, adds a property delimiter, and then adds the node's
261      * name. The name of the root node is a blank string. Note that no indices are returned.
262      */
263     @Override
264     public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler) {
265         if (parentKey == null) {
266             // this is the root node
267             return StringUtils.EMPTY;
268         }
269         final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey);
270         key.append(handler.nodeName(node), true);
271         return key.toString();
272     }
273 
274     /**
275      * <p>
276      * Prepares Adding the property with the specified key.
277      * </p>
278      * <p>
279      * To be able to deal with the structure supported by hierarchical configuration implementations the passed in key is of
280      * importance, especially the indices it might contain. The following example should clarify this: Suppose the current
281      * node structure looks like the following:
282      * </p>
283      *
284      * <pre>
285      *  tables
286      *     +-- table
287      *             +-- name = user
288      *             +-- fields
289      *                     +-- field
290      *                             +-- name = uid
291      *                     +-- field
292      *                             +-- name = firstName
293      *                     ...
294      *     +-- table
295      *             +-- name = documents
296      *             +-- fields
297      *                    ...
298      * </pre>
299      * <p>
300      * In this example a database structure is defined, for example all fields of the first table could be accessed using the key
301      * {@code tables.table(0).fields.field.name}. If now properties are to be added, it must be exactly specified at which
302      * 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
303      * to say just
304      * </p>
305      *
306      * <pre>
307      * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
308      * </pre>
309      * <p>
310      * The statement given above contains some ambiguity. For instance it is not clear, to which table the new field should
311      * be added. If this method finds such an ambiguity, it is resolved by following the last valid path. Here this would be
312      * the last table. The same is true for the {@code field}; because there are multiple fields and no explicit index is
313      * provided, a new {@code name} property would be added to the last field - which is probably not what was desired.
314      * </p>
315      * <p>
316      * To make things clear explicit indices should be provided whenever possible. In the example above the exact table
317      * could be specified by providing an index for the {@code table} element as in {@code tables.table(1).fields}. By
318      * specifying an index it can also be expressed that at a given position in the configuration tree a new branch should
319      * be added. In the example above we did not want to add an additional {@code name} element to the last field of the
320      * table, but we want a complete new {@code field} element. This can be achieved by specifying an invalid index (like
321      * -1) after the element where a new branch should be created. Given this our example would run:
322      * </p>
323      *
324      * <pre>
325      * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
326      * </pre>
327      * <p>
328      * With this notation it is possible to add new branches everywhere. We could for instance create a new {@code table}
329      * element by specifying
330      * </p>
331      *
332      * <pre>
333      * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
334      * </pre>
335      * <p>
336      * (Note that because after the {@code table} element a new branch is created indices in following elements are not
337      * relevant; the branch is new so there cannot be any ambiguities.)
338      * </p>
339      *
340      * @param <T> the type of the nodes to be dealt with
341      * @param root the root node of the nodes hierarchy
342      * @param key the key of the new property
343      * @param handler the node handler
344      * @return a data object with information needed for the add operation
345      */
346     @Override
347     public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler) {
348         final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(this, key).iterator();
349         if (!it.hasNext()) {
350             throw new IllegalArgumentException("Key for add operation must be defined.");
351         }
352 
353         final T parent = findLastPathNode(it, root, handler);
354         final List<String> pathNodes = new LinkedList<>();
355 
356         while (it.hasNext()) {
357             if (!it.isPropertyKey()) {
358                 throw new IllegalArgumentException("Invalid key for add operation: " + key + " (Attribute key in the middle.)");
359             }
360             pathNodes.add(it.currentKey());
361             it.next();
362         }
363 
364         return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), pathNodes);
365     }
366 
367     /**
368      * Called by {@code findNodesForKey()} to process the sub nodes of the current node depending on the type of the current
369      * key part (children, attributes, or both).
370      *
371      * @param <T> the type of the nodes to be dealt with
372      * @param keyPart the key part
373      * @param subNodes a list with the sub nodes to process
374      * @param nodes the target collection
375      * @param handler the node handler
376      */
377     private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart, final List<T> subNodes, final Collection<QueryResult<T>> nodes,
378         final NodeHandler<T> handler) {
379         if (keyPart.hasIndex()) {
380             if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) {
381                 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), subNodes.get(keyPart.getIndex()), nodes, handler);
382             }
383         } else {
384             subNodes.forEach(node -> findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), node, nodes, handler));
385         }
386     }
387 
388     /**
389      * {@inheritDoc} This method supports the syntax as described in the class comment.
390      */
391     @Override
392     public <T> List<QueryResult<T>> query(final T root, final String key, final NodeHandler<T> handler) {
393         final List<QueryResult<T>> results = new LinkedList<>();
394         findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), root, results, handler);
395         return results;
396     }
397 }