001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.configuration.tree;
018:
019: import java.util.Collection;
020: import java.util.Iterator;
021: import java.util.LinkedList;
022: import java.util.List;
023:
024: import org.apache.commons.lang.StringUtils;
025:
026: /**
027: * <p>
028: * A default implementation of the <code>ExpressionEngine</code> interface
029: * providing the "native"e; expression language for hierarchical
030: * configurations.
031: * </p>
032: * <p>
033: * This class implements a rather simple expression language for navigating
034: * through a hierarchy of configuration nodes. It supports the following
035: * operations:
036: * </p>
037: * <p>
038: * <ul>
039: * <li>Navigating from a node to one of its children using the child node
040: * delimiter, which is by the default a dot (".").</li>
041: * <li>Navigating from a node to one of its attributes using the attribute node
042: * delimiter, which by default follows the XPATH like syntax
043: * <code>[@<attributeName>]</code>.</li>
044: * <li>If there are multiple child or attribute nodes with the same name, a
045: * specific node can be selected using a numerical index. By default indices are
046: * written in paranthesis.</li>
047: * </ul>
048: * </p>
049: * <p>
050: * As an example consider the following XML document:
051: * </p>
052: *
053: * <pre>
054: * <database>
055: * <tables>
056: * <table type="system">
057: * <name>users</name>
058: * <fields>
059: * <field>
060: * <name>lid</name>
061: * <type>long</name>
062: * </field>
063: * <field>
064: * <name>usrName</name>
065: * <type>java.lang.String</type>
066: * </field>
067: * ...
068: * </fields>
069: * </table>
070: * <table>
071: * <name>documents</name>
072: * <fields>
073: * <field>
074: * <name>docid</name>
075: * <type>long</type>
076: * </field>
077: * ...
078: * </fields>
079: * </table>
080: * ...
081: * </tables>
082: * </database>
083: * </pre>
084: *
085: * </p>
086: * <p>
087: * If this document is parsed and stored in a hierarchical configuration object,
088: * for instance the key <code>tables.table(0).name</code> can be used to find
089: * out the name of the first table. In opposite <code>tables.table.name</code>
090: * would return a collection with the names of all available tables. Similarily
091: * the key <code>tables.table(1).fields.field.name</code> returns a collection
092: * with the names of all fields of the second table. If another index is added
093: * after the <code>field</code> element, a single field can be accessed:
094: * <code>tables.table(1).fields.field(0).name</code>. The key
095: * <code>tables.table(0)[@type]</code> would select the type attribute of the
096: * first table.
097: * </p>
098: * <p>
099: * 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: /** Constant for the default property delimiter. */
110: public static final String DEFAULT_PROPERTY_DELIMITER = ".";
111:
112: /** Constant for the default escaped property delimiter. */
113: public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
114: + DEFAULT_PROPERTY_DELIMITER;
115:
116: /** Constant for the default attribute start marker. */
117: public static final String DEFAULT_ATTRIBUTE_START = "[@";
118:
119: /** Constant for the default attribute end marker. */
120: public static final String DEFAULT_ATTRIBUTE_END = "]";
121:
122: /** Constant for the default index start marker. */
123: public static final String DEFAULT_INDEX_START = "(";
124:
125: /** Constant for the default index end marker. */
126: public static final String DEFAULT_INDEX_END = ")";
127:
128: /** Stores the property delimiter. */
129: private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
130:
131: /** Stores the escaped property delimiter. */
132: private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
133:
134: /** Stores the attribute start marker. */
135: private String attributeStart = DEFAULT_ATTRIBUTE_START;
136:
137: /** Stores the attribute end marker. */
138: private String attributeEnd = DEFAULT_ATTRIBUTE_END;
139:
140: /** Stores the index start marker. */
141: private String indexStart = DEFAULT_INDEX_START;
142:
143: /** stores the index end marker. */
144: private String indexEnd = DEFAULT_INDEX_END;
145:
146: /**
147: * Sets the attribute end marker.
148: *
149: * @return the attribute end marker
150: */
151: public String getAttributeEnd() {
152: return attributeEnd;
153: }
154:
155: /**
156: * Sets the attribute end marker.
157: *
158: * @param attributeEnd the attribute end marker; can be <b>null</b> if no
159: * end marker is needed
160: */
161: public void setAttributeEnd(String attributeEnd) {
162: this .attributeEnd = attributeEnd;
163: }
164:
165: /**
166: * Returns the attribute start marker.
167: *
168: * @return the attribute start marker
169: */
170: public String getAttributeStart() {
171: return attributeStart;
172: }
173:
174: /**
175: * Sets the attribute start marker. Attribute start and end marker are used
176: * together to detect attributes in a property key.
177: *
178: * @param attributeStart the attribute start marker
179: */
180: public void setAttributeStart(String attributeStart) {
181: this .attributeStart = attributeStart;
182: }
183:
184: /**
185: * Returns the escaped property delimiter string.
186: *
187: * @return the escaped property delimiter
188: */
189: public String getEscapedDelimiter() {
190: return escapedDelimiter;
191: }
192:
193: /**
194: * Sets the escaped property delimiter string. With this string a delimiter
195: * that belongs to the key of a property can be escaped. If for instance
196: * "." is used as property delimiter, you can set the escaped
197: * delimiter to "\." and can then escape the delimiter with a back
198: * slash.
199: *
200: * @param escapedDelimiter the escaped delimiter string
201: */
202: public void setEscapedDelimiter(String escapedDelimiter) {
203: this .escapedDelimiter = escapedDelimiter;
204: }
205:
206: /**
207: * Returns the index end marker.
208: *
209: * @return the index end marker
210: */
211: public String getIndexEnd() {
212: return indexEnd;
213: }
214:
215: /**
216: * Sets the index end marker.
217: *
218: * @param indexEnd the index end marker
219: */
220: public void setIndexEnd(String indexEnd) {
221: this .indexEnd = indexEnd;
222: }
223:
224: /**
225: * Returns the index start marker.
226: *
227: * @return the index start marker
228: */
229: public String getIndexStart() {
230: return indexStart;
231: }
232:
233: /**
234: * Sets the index start marker. Index start and end marker are used together
235: * to detect indices in a property key.
236: *
237: * @param indexStart the index start marker
238: */
239: public void setIndexStart(String indexStart) {
240: this .indexStart = indexStart;
241: }
242:
243: /**
244: * Returns the property delimiter.
245: *
246: * @return the property delimiter
247: */
248: public String getPropertyDelimiter() {
249: return propertyDelimiter;
250: }
251:
252: /**
253: * Sets the property delmiter. This string is used to split the parts of a
254: * property key.
255: *
256: * @param propertyDelimiter the property delimiter
257: */
258: public void setPropertyDelimiter(String propertyDelimiter) {
259: this .propertyDelimiter = propertyDelimiter;
260: }
261:
262: /**
263: * Evaluates the given key and returns all matching nodes. This method
264: * supports the syntax as described in the class comment.
265: *
266: * @param root the root node
267: * @param key the key
268: * @return a list with the matching nodes
269: */
270: public List query(ConfigurationNode root, String key) {
271: List nodes = new LinkedList();
272: findNodesForKey(new DefaultConfigurationKey(this , key)
273: .iterator(), root, nodes);
274: return nodes;
275: }
276:
277: /**
278: * Determines the key of the passed in node. This implementation takes the
279: * given parent key, adds a property delimiter, and then adds the node's
280: * name. (For attribute nodes the attribute delimiters are used instead.)
281: * The name of the root node is a blanc string. Note that no indices will be
282: * returned.
283: *
284: * @param node the node whose key is to be determined
285: * @param parentKey the key of this node's parent
286: * @return the key for the given node
287: */
288: public String nodeKey(ConfigurationNode node, String parentKey) {
289: if (parentKey == null) {
290: // this is the root node
291: return StringUtils.EMPTY;
292: }
293:
294: else {
295: DefaultConfigurationKey key = new DefaultConfigurationKey(
296: this , parentKey);
297: if (node.isAttribute()) {
298: key.appendAttribute(node.getName());
299: } else {
300: key.append(node.getName(), true);
301: }
302: return key.toString();
303: }
304: }
305:
306: /**
307: * <p>
308: * Prepares Adding the property with the specified key.
309: * </p>
310: * <p>
311: * To be able to deal with the structure supported by hierarchical
312: * configuration implementations the passed in key is of importance,
313: * especially the indices it might contain. The following example should
314: * clearify this: Suppose the actual node structure looks like the
315: * following:
316: * </p>
317: * <p>
318: * <pre>
319: * tables
320: * +-- table
321: * +-- name = user
322: * +-- fields
323: * +-- field
324: * +-- name = uid
325: * +-- field
326: * +-- name = firstName
327: * ...
328: * +-- table
329: * +-- name = documents
330: * +-- fields
331: * ...
332: * </pre>
333: * </p>
334: * <p>
335: * In this example a database structure is defined, e.g. all fields of the
336: * first table could be accessed using the key
337: * <code>tables.table(0).fields.field.name</code>. If now properties are
338: * to be added, it must be exactly specified at which position in the
339: * hierarchy the new property is to be inserted. So to add a new field name
340: * to a table it is not enough to say just
341: * </p>
342: * <p>
343: * <pre>
344: * config.addProperty("tables.table.fields.field.name", "newField");
345: * </pre>
346: * </p>
347: * <p>
348: * The statement given above contains some ambiguity. For instance it is not
349: * clear, to which table the new field should be added. If this method finds
350: * such an ambiguity, it is resolved by following the last valid path. Here
351: * this would be the last table. The same is true for the <code>field</code>;
352: * because there are multiple fields and no explicit index is provided, a
353: * new <code>name</code> property would be added to the last field - which
354: * is propably not what was desired.
355: * </p>
356: * <p>
357: * To make things clear explicit indices should be provided whenever
358: * possible. In the example above the exact table could be specified by
359: * providing an index for the <code>table</code> element as in
360: * <code>tables.table(1).fields</code>. By specifying an index it can
361: * also be expressed that at a given position in the configuration tree a
362: * new branch should be added. In the example above we did not want to add
363: * an additional <code>name</code> element to the last field of the table,
364: * but we want a complete new <code>field</code> element. This can be
365: * achieved by specifying an invalid index (like -1) after the element where
366: * a new branch should be created. Given this our example would run:
367: * </p>
368: * <p>
369: * <pre>
370: * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
371: * </pre>
372: * </p>
373: * <p>
374: * With this notation it is possible to add new branches everywhere. We
375: * could for instance create a new <code>table</code> element by
376: * specifying
377: * </p>
378: * <p>
379: * <pre>
380: * config.addProperty("tables.table(-1).fields.field.name", "newField2");
381: * </pre>
382: * </p>
383: * <p>
384: * (Note that because after the <code>table</code> element a new branch is
385: * created indices in following elements are not relevant; the branch is new
386: * so there cannot be any ambiguities.)
387: * </p>
388: *
389: * @param root the root node of the nodes hierarchy
390: * @param key the key of the new property
391: * @return a data object with information needed for the add operation
392: */
393: public NodeAddData prepareAdd(ConfigurationNode root, String key) {
394: DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
395: this , key).iterator();
396: if (!it.hasNext()) {
397: throw new IllegalArgumentException(
398: "Key for add operation must be defined!");
399: }
400:
401: NodeAddData result = new NodeAddData();
402: result.setParent(findLastPathNode(it, root));
403:
404: while (it.hasNext()) {
405: if (!it.isPropertyKey()) {
406: throw new IllegalArgumentException(
407: "Invalid key for add operation: " + key
408: + " (Attribute key in the middle.)");
409: }
410: result.addPathNode(it.currentKey());
411: it.next();
412: }
413:
414: result.setNewNodeName(it.currentKey());
415: result.setAttribute(!it.isPropertyKey());
416: return result;
417: }
418:
419: /**
420: * Recursive helper method for evaluating a key. This method processes all
421: * facets of a configuration key, traverses the tree of properties and
422: * fetches the the nodes of all matching properties.
423: *
424: * @param keyPart the configuration key iterator
425: * @param node the actual node
426: * @param nodes here the found nodes are stored
427: */
428: protected void findNodesForKey(
429: DefaultConfigurationKey.KeyIterator keyPart,
430: ConfigurationNode node, Collection nodes) {
431: if (!keyPart.hasNext()) {
432: nodes.add(node);
433: }
434:
435: else {
436: String key = keyPart.nextKey(false);
437: if (keyPart.isPropertyKey()) {
438: processSubNodes(keyPart, node.getChildren(key), nodes);
439: }
440: if (keyPart.isAttribute()) {
441: processSubNodes(keyPart, node.getAttributes(key), nodes);
442: }
443: }
444: }
445:
446: /**
447: * Finds the last existing node for an add operation. This method traverses
448: * the configuration node tree along the specified key. The last existing
449: * node on this path is returned.
450: *
451: * @param keyIt the key iterator
452: * @param node the actual node
453: * @return the last existing node on the given path
454: */
455: protected ConfigurationNode findLastPathNode(
456: DefaultConfigurationKey.KeyIterator keyIt,
457: ConfigurationNode node) {
458: String keyPart = keyIt.nextKey(false);
459:
460: if (keyIt.hasNext()) {
461: if (!keyIt.isPropertyKey()) {
462: // Attribute keys can only appear as last elements of the path
463: throw new IllegalArgumentException(
464: "Invalid path for add operation: "
465: + "Attribute key in the middle!");
466: }
467: int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
468: .getChildrenCount(keyPart) - 1;
469: if (idx < 0 || idx >= node.getChildrenCount(keyPart)) {
470: return node;
471: } else {
472: return findLastPathNode(keyIt, (ConfigurationNode) node
473: .getChildren(keyPart).get(idx));
474: }
475: }
476:
477: else {
478: return node;
479: }
480: }
481:
482: /**
483: * Called by <code>findNodesForKey()</code> to process the sub nodes of
484: * the current node depending on the type of the current key part (children,
485: * attributes, or both).
486: *
487: * @param keyPart the key part
488: * @param subNodes a list with the sub nodes to process
489: * @param nodes the target collection
490: */
491: private void processSubNodes(
492: DefaultConfigurationKey.KeyIterator keyPart, List subNodes,
493: Collection nodes) {
494: if (keyPart.hasIndex()) {
495: if (keyPart.getIndex() >= 0
496: && keyPart.getIndex() < subNodes.size()) {
497: findNodesForKey(
498: (DefaultConfigurationKey.KeyIterator) keyPart
499: .clone(), (ConfigurationNode) subNodes
500: .get(keyPart.getIndex()), nodes);
501: }
502: } else {
503: for (Iterator it = subNodes.iterator(); it.hasNext();) {
504: findNodesForKey(
505: (DefaultConfigurationKey.KeyIterator) keyPart
506: .clone(),
507: (ConfigurationNode) it.next(), nodes);
508: }
509: }
510: }
511: }
|