##### Coverage Report - org.apache.commons.configuration.tree.DefaultExpressionEngine

Classes in this File | Line Coverage | Branch Coverage | Complexity
DefaultExpressionEngine |
100% | 71/71
100% | 16/16
| 2.111111111111111;2,111

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 | 158 |
     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 | 79 |
         private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
131 |  |
132 |  |
         /** Stores the escaped property delimiter. */
133 | 79 |
         private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
134 |  |
135 |  |
         /** Stores the attribute start marker. */
136 | 79 |
         private String attributeStart = DEFAULT_ATTRIBUTE_START;
137 |  |
138 |  |
         /** Stores the attribute end marker. */
139 | 79 |
         private String attributeEnd = DEFAULT_ATTRIBUTE_END;
140 |  |
141 |  |
         /** Stores the index start marker. */
142 | 79 |
         private String indexStart = DEFAULT_INDEX_START;
143 |  |
144 |  |
         /** stores the index end marker. */
145 | 79 |
         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 | 10237 |
             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 | 8 |
             this.attributeEnd = attributeEnd;
166 | 8 |
         }
167 |  |
168 |  |
         /**
169 |  |
          * Returns the attribute start marker.
170 |  |
          *
171 |  |
          * @return the attribute start marker
172 |  |
          */
173 |  |
         public String getAttributeStart()
174 |  |
         {
175 | 25483 |
             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 | 9 |
             this.attributeStart = attributeStart;
187 | 9 |
         }
188 |  |
189 |  |
         /**
190 |  |
          * Returns the escaped property delimiter string.
191 |  |
          *
192 |  |
          * @return the escaped property delimiter
193 |  |
          */
194 |  |
         public String getEscapedDelimiter()
195 |  |
         {
196 | 56852 |
             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 | 6 |
             this.escapedDelimiter = escapedDelimiter;
211 | 6 |
         }
212 |  |
213 |  |
         /**
214 |  |
          * Returns the index end marker.
215 |  |
          *
216 |  |
          * @return the index end marker
217 |  |
          */
218 |  |
         public String getIndexEnd()
219 |  |
         {
220 | 485 |
             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 | 8 |
             this.indexEnd = indexEnd;
231 | 8 |
         }
232 |  |
233 |  |
         /**
234 |  |
          * Returns the index start marker.
235 |  |
          *
236 |  |
          * @return the index start marker
237 |  |
          */
238 |  |
         public String getIndexStart()
239 |  |
         {
240 | 11843 |
             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 | 8 |
             this.indexStart = indexStart;
252 | 8 |
         }
253 |  |
254 |  |
         /**
255 |  |
          * Returns the property delimiter.
256 |  |
          *
257 |  |
          * @return the property delimiter
258 |  |
          */
259 |  |
         public String getPropertyDelimiter()
260 |  |
         {
261 | 69236 |
             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 | 8 |
             this.propertyDelimiter = propertyDelimiter;
273 | 8 |
         }
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 | 2926 |
             List nodes = new LinkedList();
286 | 2926 |
             findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
287 |  |
                     root, nodes);
288 | 2926 |
             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 | 932 |
             if (parentKey == null)
305 |  |
             {
306 |  |
                 // this is the root node
307 | 38 |
                 return StringUtils.EMPTY;
308 |  |
             }
309 |  |
310 |  |
             else
311 |  |
             {
312 | 894 |
                 DefaultConfigurationKey key = new DefaultConfigurationKey(this,
313 |  |
                         parentKey);
314 | 894 |
                 if (node.isAttribute())
315 |  |
                 {
316 | 146 |
                     key.appendAttribute(node.getName());
317 |  |
                 }
318 |  |
                 else
319 |  |
                 {
320 | 748 |
                     key.append(node.getName(), true);
321 |  |
                 }
322 | 894 |
                 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 | 1698 |
             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
416 |  |
                     this, key).iterator();
417 | 1698 |
             if (!it.hasNext())
418 |  |
             {
419 | 3 |
                 throw new IllegalArgumentException(
420 |  |
                         "Key for add operation must be defined!");
421 |  |
             }
422 |  |
423 | 1695 |
             NodeAddData result = new NodeAddData();
424 | 1695 |
             result.setParent(findLastPathNode(it, root));
425 |  |
426 | 4176 |
             while (it.hasNext())
427 |  |
             {
428 | 789 |
                 if (!it.isPropertyKey())
429 |  |
                 {
430 | 1 |
                     throw new IllegalArgumentException(
431 |  |
                             "Invalid key for add operation: " + key
432 |  |
                                     + " (Attribute key in the middle.)");
433 |  |
                 }
434 | 788 |
                 result.addPathNode(it.currentKey());
435 | 788 |
                 it.next();
436 |  |
             }
437 |  |
438 | 1693 |
             result.setNewNodeName(it.currentKey());
439 | 1693 |
             result.setAttribute(!it.isPropertyKey());
440 | 1693 |
             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 | 9967 |
             if (!keyPart.hasNext())
456 |  |
             {
457 | 2780 |
                 nodes.add(node);
458 |  |
             }
459 |  |
460 |  |
             else
461 |  |
             {
462 | 7187 |
                 String key = keyPart.nextKey(false);
463 | 7187 |
                 if (keyPart.isPropertyKey())
464 |  |
                 {
465 | 6370 |
                     processSubNodes(keyPart, node.getChildren(key), nodes);
466 |  |
                 }
467 | 7187 |
                 if (keyPart.isAttribute())
468 |  |
                 {
469 | 821 |
                     processSubNodes(keyPart, node.getAttributes(key), nodes);
470 |  |
                 }
471 |  |
             }
472 | 9967 |
         }
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 | 3827 |
             String keyPart = keyIt.nextKey(false);
487 |  |
488 | 3827 |
             if (keyIt.hasNext())
489 |  |
             {
490 | 2768 |
                 if (!keyIt.isPropertyKey())
491 |  |
                 {
492 |  |
                     // Attribute keys can only appear as last elements of the path
493 | 1 |
                     throw new IllegalArgumentException(
494 |  |
                             "Invalid path for add operation: "
495 |  |
                                     + "Attribute key in the middle!");
496 |  |
                 }
497 | 2767 |
                 int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
498 |  |
                         .getChildrenCount(keyPart) - 1;
499 | 2767 |
                 if (idx < 0 || idx >= node.getChildrenCount(keyPart))
500 |  |
                 {
501 | 635 |
                     return node;
502 |  |
                 }
503 |  |
                 else
504 |  |
                 {
505 | 2132 |
                     return findLastPathNode(keyIt, (ConfigurationNode) node
506 |  |
                             .getChildren(keyPart).get(idx));
507 |  |
                 }
508 |  |
             }
509 |  |
510 |  |
             else
511 |  |
             {
512 | 1059 |
                 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 | 7191 |
             if (keyPart.hasIndex())
529 |  |
             {
530 | 271 |
                 if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
531 |  |
                 {
532 | 264 |
                     findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
533 |  |
                             .clone(), (ConfigurationNode) subNodes.get(keyPart
534 |  |
                             .getIndex()), nodes);
535 |  |
                 }
536 |  |
             }
537 |  |
             else
538 |  |
             {
539 | 20617 |
                 for (Iterator it = subNodes.iterator(); it.hasNext();)
540 |  |
                 {
541 | 6777 |
                     findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
542 |  |
                             .clone(), (ConfigurationNode) it.next(), nodes);
543 |  |
                 }
544 |  |
             }
545 | 7191 |
         }
546 |  |
     }