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