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 "native" 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 * (".").</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 [@<attributeName>]}.</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 * <database>
48 * <tables>
49 * <table type="system">
50 * <name>users</name>
51 * <fields>
52 * <field>
53 * <name>lid</name>
54 * <type>long</name>
55 * </field>
56 * <field>
57 * <name>usrName</name>
58 * <type>java.lang.String</type>
59 * </field>
60 * ...
61 * </fields>
62 * </table>
63 * <table>
64 * <name>documents</name>
65 * <fields>
66 * <field>
67 * <name>docid</name>
68 * <type>long</type>
69 * </field>
70 * ...
71 * </fields>
72 * </table>
73 * ...
74 * </tables>
75 * </database>
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("tables.table.fields.field.name", "newField");
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("tables.table(1).fields.field(-1).name", "newField");
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("tables.table(-1).fields.field.name", "newField2");
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 }