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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package org.apache.commons.configuration.tree;
19
import java.util.Collection;
20
import java.util.LinkedList;
21
import java.util.List;
23
import org.apache.commons.lang.StringUtils;
27
* A default implementation of the {@code ExpressionEngine} interface
28
* providing the "native"e; expression language for hierarchical
32
* This class implements a rather simple expression language for navigating
33
* through a hierarchy of configuration nodes. It supports the following
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>
49
* As an example consider the following XML document:
55
* <table type="system">
56
* <name>users</name>
59
* <name>lid</name>
60
* <type>long</name>
63
* <name>usrName</name>
64
* <type>java.lang.String</type>
70
* <name>documents</name>
73
* <name>docid</name>
74
* <type>long</type>
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
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.
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 $
109
public class DefaultExpressionEngine implements ExpressionEngine
111
/** Constant for the default property delimiter. */
112
public static final String DEFAULT_PROPERTY_DELIMITER = ".";
114
/** Constant for the default escaped property delimiter. */
115
public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
116
+ DEFAULT_PROPERTY_DELIMITER;
118
/** Constant for the default attribute start marker. */
119
public static final String DEFAULT_ATTRIBUTE_START = "[@";
121
/** Constant for the default attribute end marker. */
122
public static final String DEFAULT_ATTRIBUTE_END = "]";
124
/** Constant for the default index start marker. */
125
public static final String DEFAULT_INDEX_START = "(";
127
/** Constant for the default index end marker. */
128
public static final String DEFAULT_INDEX_END = ")";
130
/** Stores the property delimiter. */
131
private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
133
/** Stores the escaped property delimiter. */
134
private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
136
/** Stores the attribute start marker. */
137
private String attributeStart = DEFAULT_ATTRIBUTE_START;
139
/** Stores the attribute end marker. */
140
private String attributeEnd = DEFAULT_ATTRIBUTE_END;
142
/** Stores the index start marker. */
143
private String indexStart = DEFAULT_INDEX_START;
145
/** stores the index end marker. */
146
private String indexEnd = DEFAULT_INDEX_END;
149
* Sets the attribute end marker.
151
* @return the attribute end marker
153
public String getAttributeEnd()
159
* Sets the attribute end marker.
161
* @param attributeEnd the attribute end marker; can be <b>null</b> if no
162
* end marker is needed
164
public void setAttributeEnd(String attributeEnd)
166
this.attributeEnd = attributeEnd;
170
* Returns the attribute start marker.
172
* @return the attribute start marker
174
public String getAttributeStart()
176
return attributeStart;
180
* Sets the attribute start marker. Attribute start and end marker are used
181
* together to detect attributes in a property key.
183
* @param attributeStart the attribute start marker
185
public void setAttributeStart(String attributeStart)
187
this.attributeStart = attributeStart;
191
* Returns the escaped property delimiter string.
193
* @return the escaped property delimiter
195
public String getEscapedDelimiter()
197
return escapedDelimiter;
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
207
* @param escapedDelimiter the escaped delimiter string
209
public void setEscapedDelimiter(String escapedDelimiter)
211
this.escapedDelimiter = escapedDelimiter;
215
* Returns the index end marker.
217
* @return the index end marker
219
public String getIndexEnd()
225
* Sets the index end marker.
227
* @param indexEnd the index end marker
229
public void setIndexEnd(String indexEnd)
231
this.indexEnd = indexEnd;
235
* Returns the index start marker.
237
* @return the index start marker
239
public String getIndexStart()
245
* Sets the index start marker. Index start and end marker are used together
246
* to detect indices in a property key.
248
* @param indexStart the index start marker
250
public void setIndexStart(String indexStart)
252
this.indexStart = indexStart;
256
* Returns the property delimiter.
258
* @return the property delimiter
260
public String getPropertyDelimiter()
262
return propertyDelimiter;
266
* Sets the property delimiter. This string is used to split the parts of a
269
* @param propertyDelimiter the property delimiter
271
public void setPropertyDelimiter(String propertyDelimiter)
273
this.propertyDelimiter = propertyDelimiter;
277
* Evaluates the given key and returns all matching nodes. This method
278
* supports the syntax as described in the class comment.
280
* @param root the root node
282
* @return a list with the matching nodes
284
public List<ConfigurationNode> query(ConfigurationNode root, String key)
286
List<ConfigurationNode> nodes = new LinkedList<ConfigurationNode>();
287
findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
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
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
303
public String nodeKey(ConfigurationNode node, String parentKey)
305
if (parentKey == null)
307
// this is the root node
308
return StringUtils.EMPTY;
313
DefaultConfigurationKey key = new DefaultConfigurationKey(this,
315
if (node.isAttribute())
317
key.appendAttribute(node.getName());
321
key.append(node.getName(), true);
323
return key.toString();
329
* Prepares Adding the property with the specified key.
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
347
* +-- name = firstName
350
* +-- name = documents
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
365
* config.addProperty("tables.table.fields.field.name", "newField");
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.
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:
391
* config.addProperty("tables.table(1).fields.field(-1).name", "newField");
395
* With this notation it is possible to add new branches everywhere. We
396
* could for instance create a new {@code table} element by
401
* config.addProperty("tables.table(-1).fields.field.name", "newField2");
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.)
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
414
public NodeAddData prepareAdd(ConfigurationNode root, String key)
416
DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
417
this, key).iterator();
420
throw new IllegalArgumentException(
421
"Key for add operation must be defined!");
424
NodeAddData result = new NodeAddData();
425
result.setParent(findLastPathNode(it, root));
429
if (!it.isPropertyKey())
431
throw new IllegalArgumentException(
432
"Invalid key for add operation: " + key
433
+ " (Attribute key in the middle.)");
435
result.addPathNode(it.currentKey());
439
result.setNewNodeName(it.currentKey());
440
result.setAttribute(!it.isPropertyKey());
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.
449
* @param keyPart the configuration key iterator
450
* @param node the actual node
451
* @param nodes here the found nodes are stored
453
protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart,
454
ConfigurationNode node, Collection<ConfigurationNode> nodes)
456
if (!keyPart.hasNext())
463
String key = keyPart.nextKey(false);
464
if (keyPart.isPropertyKey())
466
processSubNodes(keyPart, node.getChildren(key), nodes);
468
if (keyPart.isAttribute())
470
processSubNodes(keyPart, node.getAttributes(key), nodes);
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.
480
* @param keyIt the key iterator
481
* @param node the actual node
482
* @return the last existing node on the given path
484
protected ConfigurationNode findLastPathNode(
485
DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node)
487
String keyPart = keyIt.nextKey(false);
491
if (!keyIt.isPropertyKey())
493
// Attribute keys can only appear as last elements of the path
494
throw new IllegalArgumentException(
495
"Invalid path for add operation: "
496
+ "Attribute key in the middle!");
498
int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
499
.getChildrenCount(keyPart) - 1;
500
if (idx < 0 || idx >= node.getChildrenCount(keyPart))
506
return findLastPathNode(keyIt, node.getChildren(keyPart).get(idx));
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).
521
* @param keyPart the key part
522
* @param subNodes a list with the sub nodes to process
523
* @param nodes the target collection
525
private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart,
526
List<ConfigurationNode> subNodes, Collection<ConfigurationNode> nodes)
528
if (keyPart.hasIndex())
530
if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
532
findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
533
.clone(), subNodes.get(keyPart.getIndex()), nodes);
538
for (ConfigurationNode node : subNodes)
540
findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
541
.clone(), node, nodes);