Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DefaultExpressionEngine |
|
| 2.2777777777777777;2,278 |
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.configuration.tree; | |
18 | ||
19 | import java.util.Collection; | |
20 | import java.util.LinkedList; | |
21 | import java.util.List; | |
22 | ||
23 | import org.apache.commons.lang.StringUtils; | |
24 | ||
25 | /** | |
26 | * <p> | |
27 | * A default implementation of the {@code ExpressionEngine} interface | |
28 | * providing the "native"e; expression language for hierarchical | |
29 | * configurations. | |
30 | * </p> | |
31 | * <p> | |
32 | * This class implements a rather simple expression language for navigating | |
33 | * through a hierarchy of configuration nodes. It supports the following | |
34 | * operations: | |
35 | * </p> | |
36 | * <p> | |
37 | * <ul> | |
38 | * <li>Navigating from a node to one of its children using the child node | |
39 | * delimiter, which is by the default a dot (".").</li> | |
40 | * <li>Navigating from a node to one of its attributes using the attribute node | |
41 | * delimiter, which by default follows the XPATH like syntax | |
42 | * <code>[@<attributeName>]</code>.</li> | |
43 | * <li>If there are multiple child or attribute nodes with the same name, a | |
44 | * specific node can be selected using a numerical index. By default indices are | |
45 | * written in parenthesis.</li> | |
46 | * </ul> | |
47 | * </p> | |
48 | * <p> | |
49 | * As an example consider the following XML document: | |
50 | * </p> | |
51 | * | |
52 | * <pre> | |
53 | * <database> | |
54 | * <tables> | |
55 | * <table type="system"> | |
56 | * <name>users</name> | |
57 | * <fields> | |
58 | * <field> | |
59 | * <name>lid</name> | |
60 | * <type>long</name> | |
61 | * </field> | |
62 | * <field> | |
63 | * <name>usrName</name> | |
64 | * <type>java.lang.String</type> | |
65 | * </field> | |
66 | * ... | |
67 | * </fields> | |
68 | * </table> | |
69 | * <table> | |
70 | * <name>documents</name> | |
71 | * <fields> | |
72 | * <field> | |
73 | * <name>docid</name> | |
74 | * <type>long</type> | |
75 | * </field> | |
76 | * ... | |
77 | * </fields> | |
78 | * </table> | |
79 | * ... | |
80 | * </tables> | |
81 | * </database> | |
82 | * </pre> | |
83 | * | |
84 | * </p> | |
85 | * <p> | |
86 | * If this document is parsed and stored in a hierarchical configuration object, | |
87 | * for instance the key {@code tables.table(0).name} can be used to find | |
88 | * out the name of the first table. In opposite {@code tables.table.name} | |
89 | * would return a collection with the names of all available tables. Similarly | |
90 | * the key {@code tables.table(1).fields.field.name} returns a collection | |
91 | * with the names of all fields of the second table. If another index is added | |
92 | * after the {@code field} element, a single field can be accessed: | |
93 | * {@code tables.table(1).fields.field(0).name}. The key | |
94 | * {@code tables.table(0)[@type]} would select the type attribute of the | |
95 | * first table. | |
96 | * </p> | |
97 | * <p> | |
98 | * This example works with the default values for delimiters and index markers. | |
99 | * It is also possible to set custom values for these properties so that you can | |
100 | * adapt a {@code DefaultExpressionEngine} to your personal needs. | |
101 | * </p> | |
102 | * | |
103 | * @since 1.3 | |
104 | * @author <a | |
105 | * href="http://commons.apache.org/configuration/team-list.html">Commons | |
106 | * Configuration team</a> | |
107 | * @version $Id: DefaultExpressionEngine.java 1301991 2012-03-17 20:18:02Z sebb $ | |
108 | */ | |
109 | 88 | public class DefaultExpressionEngine implements ExpressionEngine |
110 | { | |
111 | /** Constant for the default property delimiter. */ | |
112 | public static final String DEFAULT_PROPERTY_DELIMITER = "."; | |
113 | ||
114 | /** Constant for the default escaped property delimiter. */ | |
115 | public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER | |
116 | + DEFAULT_PROPERTY_DELIMITER; | |
117 | ||
118 | /** Constant for the default attribute start marker. */ | |
119 | public static final String DEFAULT_ATTRIBUTE_START = "[@"; | |
120 | ||
121 | /** Constant for the default attribute end marker. */ | |
122 | public static final String DEFAULT_ATTRIBUTE_END = "]"; | |
123 | ||
124 | /** Constant for the default index start marker. */ | |
125 | public static final String DEFAULT_INDEX_START = "("; | |
126 | ||
127 | /** Constant for the default index end marker. */ | |
128 | public static final String DEFAULT_INDEX_END = ")"; | |
129 | ||
130 | /** Stores the property delimiter. */ | |
131 | 88 | private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER; |
132 | ||
133 | /** Stores the escaped property delimiter. */ | |
134 | 88 | private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER; |
135 | ||
136 | /** Stores the attribute start marker. */ | |
137 | 88 | private String attributeStart = DEFAULT_ATTRIBUTE_START; |
138 | ||
139 | /** Stores the attribute end marker. */ | |
140 | 88 | private String attributeEnd = DEFAULT_ATTRIBUTE_END; |
141 | ||
142 | /** Stores the index start marker. */ | |
143 | 88 | private String indexStart = DEFAULT_INDEX_START; |
144 | ||
145 | /** stores the index end marker. */ | |
146 | 88 | private String indexEnd = DEFAULT_INDEX_END; |
147 | ||
148 | /** | |
149 | * Sets the attribute end marker. | |
150 | * | |
151 | * @return the attribute end marker | |
152 | */ | |
153 | public String getAttributeEnd() | |
154 | { | |
155 | 79511 | return attributeEnd; |
156 | } | |
157 | ||
158 | /** | |
159 | * Sets the attribute end marker. | |
160 | * | |
161 | * @param attributeEnd the attribute end marker; can be <b>null</b> if no | |
162 | * end marker is needed | |
163 | */ | |
164 | public void setAttributeEnd(String attributeEnd) | |
165 | { | |
166 | 8 | this.attributeEnd = attributeEnd; |
167 | 8 | } |
168 | ||
169 | /** | |
170 | * Returns the attribute start marker. | |
171 | * | |
172 | * @return the attribute start marker | |
173 | */ | |
174 | public String getAttributeStart() | |
175 | { | |
176 | 184705 | return attributeStart; |
177 | } | |
178 | ||
179 | /** | |
180 | * Sets the attribute start marker. Attribute start and end marker are used | |
181 | * together to detect attributes in a property key. | |
182 | * | |
183 | * @param attributeStart the attribute start marker | |
184 | */ | |
185 | public void setAttributeStart(String attributeStart) | |
186 | { | |
187 | 9 | this.attributeStart = attributeStart; |
188 | 9 | } |
189 | ||
190 | /** | |
191 | * Returns the escaped property delimiter string. | |
192 | * | |
193 | * @return the escaped property delimiter | |
194 | */ | |
195 | public String getEscapedDelimiter() | |
196 | { | |
197 | 273595 | return escapedDelimiter; |
198 | } | |
199 | ||
200 | /** | |
201 | * Sets the escaped property delimiter string. With this string a delimiter | |
202 | * that belongs to the key of a property can be escaped. If for instance | |
203 | * "." is used as property delimiter, you can set the escaped | |
204 | * delimiter to "\." and can then escape the delimiter with a back | |
205 | * slash. | |
206 | * | |
207 | * @param escapedDelimiter the escaped delimiter string | |
208 | */ | |
209 | public void setEscapedDelimiter(String escapedDelimiter) | |
210 | { | |
211 | 6 | this.escapedDelimiter = escapedDelimiter; |
212 | 6 | } |
213 | ||
214 | /** | |
215 | * Returns the index end marker. | |
216 | * | |
217 | * @return the index end marker | |
218 | */ | |
219 | public String getIndexEnd() | |
220 | { | |
221 | 859 | return indexEnd; |
222 | } | |
223 | ||
224 | /** | |
225 | * Sets the index end marker. | |
226 | * | |
227 | * @param indexEnd the index end marker | |
228 | */ | |
229 | public void setIndexEnd(String indexEnd) | |
230 | { | |
231 | 25 | this.indexEnd = indexEnd; |
232 | 25 | } |
233 | ||
234 | /** | |
235 | * Returns the index start marker. | |
236 | * | |
237 | * @return the index start marker | |
238 | */ | |
239 | public String getIndexStart() | |
240 | { | |
241 | 88771 | return indexStart; |
242 | } | |
243 | ||
244 | /** | |
245 | * Sets the index start marker. Index start and end marker are used together | |
246 | * to detect indices in a property key. | |
247 | * | |
248 | * @param indexStart the index start marker | |
249 | */ | |
250 | public void setIndexStart(String indexStart) | |
251 | { | |
252 | 25 | this.indexStart = indexStart; |
253 | 25 | } |
254 | ||
255 | /** | |
256 | * Returns the property delimiter. | |
257 | * | |
258 | * @return the property delimiter | |
259 | */ | |
260 | public String getPropertyDelimiter() | |
261 | { | |
262 | 472892 | return propertyDelimiter; |
263 | } | |
264 | ||
265 | /** | |
266 | * Sets the property delimiter. This string is used to split the parts of a | |
267 | * property key. | |
268 | * | |
269 | * @param propertyDelimiter the property delimiter | |
270 | */ | |
271 | public void setPropertyDelimiter(String propertyDelimiter) | |
272 | { | |
273 | 22 | this.propertyDelimiter = propertyDelimiter; |
274 | 22 | } |
275 | ||
276 | /** | |
277 | * Evaluates the given key and returns all matching nodes. This method | |
278 | * supports the syntax as described in the class comment. | |
279 | * | |
280 | * @param root the root node | |
281 | * @param key the key | |
282 | * @return a list with the matching nodes | |
283 | */ | |
284 | public List<ConfigurationNode> query(ConfigurationNode root, String key) | |
285 | { | |
286 | 57046 | List<ConfigurationNode> nodes = new LinkedList<ConfigurationNode>(); |
287 | 57046 | findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), |
288 | root, nodes); | |
289 | 57046 | return nodes; |
290 | } | |
291 | ||
292 | /** | |
293 | * Determines the key of the passed in node. This implementation takes the | |
294 | * given parent key, adds a property delimiter, and then adds the node's | |
295 | * name. (For attribute nodes the attribute delimiters are used instead.) | |
296 | * The name of the root node is a blanc string. Note that no indices will be | |
297 | * returned. | |
298 | * | |
299 | * @param node the node whose key is to be determined | |
300 | * @param parentKey the key of this node's parent | |
301 | * @return the key for the given node | |
302 | */ | |
303 | public String nodeKey(ConfigurationNode node, String parentKey) | |
304 | { | |
305 | 3013 | if (parentKey == null) |
306 | { | |
307 | // this is the root node | |
308 | 78 | return StringUtils.EMPTY; |
309 | } | |
310 | ||
311 | else | |
312 | { | |
313 | 2935 | DefaultConfigurationKey key = new DefaultConfigurationKey(this, |
314 | parentKey); | |
315 | 2935 | if (node.isAttribute()) |
316 | { | |
317 | 727 | key.appendAttribute(node.getName()); |
318 | } | |
319 | else | |
320 | { | |
321 | 2208 | key.append(node.getName(), true); |
322 | } | |
323 | 2935 | return key.toString(); |
324 | } | |
325 | } | |
326 | ||
327 | /** | |
328 | * <p> | |
329 | * Prepares Adding the property with the specified key. | |
330 | * </p> | |
331 | * <p> | |
332 | * To be able to deal with the structure supported by hierarchical | |
333 | * configuration implementations the passed in key is of importance, | |
334 | * especially the indices it might contain. The following example should | |
335 | * clarify this: Suppose the actual node structure looks like the | |
336 | * following: | |
337 | * </p> | |
338 | * <p> | |
339 | * <pre> | |
340 | * tables | |
341 | * +-- table | |
342 | * +-- name = user | |
343 | * +-- fields | |
344 | * +-- field | |
345 | * +-- name = uid | |
346 | * +-- field | |
347 | * +-- name = firstName | |
348 | * ... | |
349 | * +-- table | |
350 | * +-- name = documents | |
351 | * +-- fields | |
352 | * ... | |
353 | * </pre> | |
354 | * </p> | |
355 | * <p> | |
356 | * In this example a database structure is defined, e.g. all fields of the | |
357 | * first table could be accessed using the key | |
358 | * {@code tables.table(0).fields.field.name}. If now properties are | |
359 | * to be added, it must be exactly specified at which position in the | |
360 | * hierarchy the new property is to be inserted. So to add a new field name | |
361 | * to a table it is not enough to say just | |
362 | * </p> | |
363 | * <p> | |
364 | * <pre> | |
365 | * config.addProperty("tables.table.fields.field.name", "newField"); | |
366 | * </pre> | |
367 | * </p> | |
368 | * <p> | |
369 | * The statement given above contains some ambiguity. For instance it is not | |
370 | * clear, to which table the new field should be added. If this method finds | |
371 | * such an ambiguity, it is resolved by following the last valid path. Here | |
372 | * this would be the last table. The same is true for the {@code field}; | |
373 | * because there are multiple fields and no explicit index is provided, a | |
374 | * new {@code name} property would be added to the last field - which | |
375 | * is probably not what was desired. | |
376 | * </p> | |
377 | * <p> | |
378 | * To make things clear explicit indices should be provided whenever | |
379 | * possible. In the example above the exact table could be specified by | |
380 | * providing an index for the {@code table} element as in | |
381 | * {@code tables.table(1).fields}. By specifying an index it can | |
382 | * also be expressed that at a given position in the configuration tree a | |
383 | * new branch should be added. In the example above we did not want to add | |
384 | * an additional {@code name} element to the last field of the table, | |
385 | * but we want a complete new {@code field} element. This can be | |
386 | * achieved by specifying an invalid index (like -1) after the element where | |
387 | * a new branch should be created. Given this our example would run: | |
388 | * </p> | |
389 | * <p> | |
390 | * <pre> | |
391 | * config.addProperty("tables.table(1).fields.field(-1).name", "newField"); | |
392 | * </pre> | |
393 | * </p> | |
394 | * <p> | |
395 | * With this notation it is possible to add new branches everywhere. We | |
396 | * could for instance create a new {@code table} element by | |
397 | * specifying | |
398 | * </p> | |
399 | * <p> | |
400 | * <pre> | |
401 | * config.addProperty("tables.table(-1).fields.field.name", "newField2"); | |
402 | * </pre> | |
403 | * </p> | |
404 | * <p> | |
405 | * (Note that because after the {@code table} element a new branch is | |
406 | * created indices in following elements are not relevant; the branch is new | |
407 | * so there cannot be any ambiguities.) | |
408 | * </p> | |
409 | * | |
410 | * @param root the root node of the nodes hierarchy | |
411 | * @param key the key of the new property | |
412 | * @return a data object with information needed for the add operation | |
413 | */ | |
414 | public NodeAddData prepareAdd(ConfigurationNode root, String key) | |
415 | { | |
416 | 10887 | DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( |
417 | this, key).iterator(); | |
418 | 10887 | if (!it.hasNext()) |
419 | { | |
420 | 3 | throw new IllegalArgumentException( |
421 | "Key for add operation must be defined!"); | |
422 | } | |
423 | ||
424 | 10884 | NodeAddData result = new NodeAddData(); |
425 | 10884 | result.setParent(findLastPathNode(it, root)); |
426 | ||
427 | 14183 | while (it.hasNext()) |
428 | { | |
429 | 3301 | if (!it.isPropertyKey()) |
430 | { | |
431 | 1 | throw new IllegalArgumentException( |
432 | "Invalid key for add operation: " + key | |
433 | + " (Attribute key in the middle.)"); | |
434 | } | |
435 | 3300 | result.addPathNode(it.currentKey()); |
436 | 3300 | it.next(); |
437 | } | |
438 | ||
439 | 10882 | result.setNewNodeName(it.currentKey()); |
440 | 10882 | result.setAttribute(!it.isPropertyKey()); |
441 | 10882 | return result; |
442 | } | |
443 | ||
444 | /** | |
445 | * Recursive helper method for evaluating a key. This method processes all | |
446 | * facets of a configuration key, traverses the tree of properties and | |
447 | * fetches the the nodes of all matching properties. | |
448 | * | |
449 | * @param keyPart the configuration key iterator | |
450 | * @param node the actual node | |
451 | * @param nodes here the found nodes are stored | |
452 | */ | |
453 | protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart, | |
454 | ConfigurationNode node, Collection<ConfigurationNode> nodes) | |
455 | { | |
456 | 75051 | if (!keyPart.hasNext()) |
457 | { | |
458 | 9415 | nodes.add(node); |
459 | } | |
460 | ||
461 | else | |
462 | { | |
463 | 65636 | String key = keyPart.nextKey(false); |
464 | 65636 | if (keyPart.isPropertyKey()) |
465 | { | |
466 | 61736 | processSubNodes(keyPart, node.getChildren(key), nodes); |
467 | } | |
468 | 65636 | if (keyPart.isAttribute()) |
469 | { | |
470 | 3904 | processSubNodes(keyPart, node.getAttributes(key), nodes); |
471 | } | |
472 | } | |
473 | 75051 | } |
474 | ||
475 | /** | |
476 | * Finds the last existing node for an add operation. This method traverses | |
477 | * the configuration node tree along the specified key. The last existing | |
478 | * node on this path is returned. | |
479 | * | |
480 | * @param keyIt the key iterator | |
481 | * @param node the actual node | |
482 | * @return the last existing node on the given path | |
483 | */ | |
484 | protected ConfigurationNode findLastPathNode( | |
485 | DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node) | |
486 | { | |
487 | 19771 | String keyPart = keyIt.nextKey(false); |
488 | ||
489 | 19771 | if (keyIt.hasNext()) |
490 | { | |
491 | 11494 | if (!keyIt.isPropertyKey()) |
492 | { | |
493 | // Attribute keys can only appear as last elements of the path | |
494 | 1 | throw new IllegalArgumentException( |
495 | "Invalid path for add operation: " | |
496 | + "Attribute key in the middle!"); | |
497 | } | |
498 | 11493 | int idx = keyIt.hasIndex() ? keyIt.getIndex() : node |
499 | .getChildrenCount(keyPart) - 1; | |
500 | 11493 | if (idx < 0 || idx >= node.getChildrenCount(keyPart)) |
501 | { | |
502 | 2606 | return node; |
503 | } | |
504 | else | |
505 | { | |
506 | 8887 | return findLastPathNode(keyIt, node.getChildren(keyPart).get(idx)); |
507 | } | |
508 | } | |
509 | ||
510 | else | |
511 | { | |
512 | 8277 | return node; |
513 | } | |
514 | } | |
515 | ||
516 | /** | |
517 | * Called by {@code findNodesForKey()} to process the sub nodes of | |
518 | * the current node depending on the type of the current key part (children, | |
519 | * attributes, or both). | |
520 | * | |
521 | * @param keyPart the key part | |
522 | * @param subNodes a list with the sub nodes to process | |
523 | * @param nodes the target collection | |
524 | */ | |
525 | private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart, | |
526 | List<ConfigurationNode> subNodes, Collection<ConfigurationNode> nodes) | |
527 | { | |
528 | 65640 | if (keyPart.hasIndex()) |
529 | { | |
530 | 455 | if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) |
531 | { | |
532 | 445 | findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart |
533 | .clone(), subNodes.get(keyPart.getIndex()), nodes); | |
534 | } | |
535 | } | |
536 | else | |
537 | { | |
538 | 65185 | for (ConfigurationNode node : subNodes) |
539 | { | |
540 | 17560 | findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart |
541 | .clone(), node, nodes); | |
542 | } | |
543 | } | |
544 | 65640 | } |
545 | } |