View Javadoc

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.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 &quot;native&quote; 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 (&quot;.&quot;).</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>[@&lt;attributeName&gt;]</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   *  &lt;database&gt;
55   *    &lt;tables&gt;
56   *      &lt;table type=&quot;system&quot;&gt;
57   *        &lt;name&gt;users&lt;/name&gt;
58   *        &lt;fields&gt;
59   *          &lt;field&gt;
60   *            &lt;name&gt;lid&lt;/name&gt;
61   *            &lt;type&gt;long&lt;/name&gt;
62   *          &lt;/field&gt;
63   *          &lt;field&gt;
64   *            &lt;name&gt;usrName&lt;/name&gt;
65   *            &lt;type&gt;java.lang.String&lt;/type&gt;
66   *          &lt;/field&gt;
67   *         ...
68   *        &lt;/fields&gt;
69   *      &lt;/table&gt;
70   *      &lt;table&gt;
71   *        &lt;name&gt;documents&lt;/name&gt;
72   *        &lt;fields&gt;
73   *          &lt;field&gt;
74   *            &lt;name&gt;docid&lt;/name&gt;
75   *            &lt;type&gt;long&lt;/type&gt;
76   *          &lt;/field&gt;
77   *          ...
78   *        &lt;/fields&gt;
79   *      &lt;/table&gt;
80   *      ...
81   *    &lt;/tables&gt;
82   *  &lt;/database&gt;
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      * &quot;.&quot; is used as property delimiter, you can set the escaped
203      * delimiter to &quot;\.&quot; 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             // this is the root node
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(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
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(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
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(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
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                 // Attribute keys can only appear as last elements of the path
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 }