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    *     http://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      * 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 <strong>null</strong>)
118      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
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 <strong>null</strong>)
130      * @param nodeNameMatcher the matcher for node names; can be <strong>null</strong>, then a default matcher is used
131      * @throws IllegalArgumentException if the symbols are <strong>null</strong>
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 <strong>null</strong>, 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, for example 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 }