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.LinkedList;
021    import java.util.List;
022    
023    import org.apache.commons.lang.StringUtils;
024    
025    /**
026     * <p>
027     * A default implementation of the {@code ExpressionEngine} interface
028     * providing the &quot;native&quote; expression language for hierarchical
029     * configurations.
030     * </p>
031     * <p>
032     * This class implements a rather simple expression language for navigating
033     * through a hierarchy of configuration nodes. It supports the following
034     * operations:
035     * </p>
036     * <p>
037     * <ul>
038     * <li>Navigating from a node to one of its children using the child node
039     * delimiter, which is by the default a dot (&quot;.&quot;).</li>
040     * <li>Navigating from a node to one of its attributes using the attribute node
041     * delimiter, which by default follows the XPATH like syntax
042     * <code>[@&lt;attributeName&gt;]</code>.</li>
043     * <li>If there are multiple child or attribute nodes with the same name, a
044     * specific node can be selected using a numerical index. By default indices are
045     * written in parenthesis.</li>
046     * </ul>
047     * </p>
048     * <p>
049     * As an example consider the following XML document:
050     * </p>
051     *
052     * <pre>
053     *  &lt;database&gt;
054     *    &lt;tables&gt;
055     *      &lt;table type=&quot;system&quot;&gt;
056     *        &lt;name&gt;users&lt;/name&gt;
057     *        &lt;fields&gt;
058     *          &lt;field&gt;
059     *            &lt;name&gt;lid&lt;/name&gt;
060     *            &lt;type&gt;long&lt;/name&gt;
061     *          &lt;/field&gt;
062     *          &lt;field&gt;
063     *            &lt;name&gt;usrName&lt;/name&gt;
064     *            &lt;type&gt;java.lang.String&lt;/type&gt;
065     *          &lt;/field&gt;
066     *         ...
067     *        &lt;/fields&gt;
068     *      &lt;/table&gt;
069     *      &lt;table&gt;
070     *        &lt;name&gt;documents&lt;/name&gt;
071     *        &lt;fields&gt;
072     *          &lt;field&gt;
073     *            &lt;name&gt;docid&lt;/name&gt;
074     *            &lt;type&gt;long&lt;/type&gt;
075     *          &lt;/field&gt;
076     *          ...
077     *        &lt;/fields&gt;
078     *      &lt;/table&gt;
079     *      ...
080     *    &lt;/tables&gt;
081     *  &lt;/database&gt;
082     * </pre>
083     *
084     * </p>
085     * <p>
086     * If this document is parsed and stored in a hierarchical configuration object,
087     * for instance the key {@code tables.table(0).name} can be used to find
088     * out the name of the first table. In opposite {@code tables.table.name}
089     * would return a collection with the names of all available tables. Similarly
090     * the key {@code tables.table(1).fields.field.name} returns a collection
091     * with the names of all fields of the second table. If another index is added
092     * after the {@code field} element, a single field can be accessed:
093     * {@code tables.table(1).fields.field(0).name}. The key
094     * {@code tables.table(0)[@type]} would select the type attribute of the
095     * first table.
096     * </p>
097     * <p>
098     * This example works with the default values for delimiters and index markers.
099     * It is also possible to set custom values for these properties so that you can
100     * adapt a {@code DefaultExpressionEngine} to your personal needs.
101     * </p>
102     *
103     * @since 1.3
104     * @author <a
105     * href="http://commons.apache.org/configuration/team-list.html">Commons
106     * Configuration team</a>
107     * @version $Id: DefaultExpressionEngine.java 1206473 2011-11-26 16:13:27Z oheger $
108     */
109    public class DefaultExpressionEngine implements ExpressionEngine
110    {
111        /** Constant for the default property delimiter. */
112        public static final String DEFAULT_PROPERTY_DELIMITER = ".";
113    
114        /** Constant for the default escaped property delimiter. */
115        public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
116                + DEFAULT_PROPERTY_DELIMITER;
117    
118        /** Constant for the default attribute start marker. */
119        public static final String DEFAULT_ATTRIBUTE_START = "[@";
120    
121        /** Constant for the default attribute end marker. */
122        public static final String DEFAULT_ATTRIBUTE_END = "]";
123    
124        /** Constant for the default index start marker. */
125        public static final String DEFAULT_INDEX_START = "(";
126    
127        /** Constant for the default index end marker. */
128        public static final String DEFAULT_INDEX_END = ")";
129    
130        /** Stores the property delimiter. */
131        private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
132    
133        /** Stores the escaped property delimiter. */
134        private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
135    
136        /** Stores the attribute start marker. */
137        private String attributeStart = DEFAULT_ATTRIBUTE_START;
138    
139        /** Stores the attribute end marker. */
140        private String attributeEnd = DEFAULT_ATTRIBUTE_END;
141    
142        /** Stores the index start marker. */
143        private String indexStart = DEFAULT_INDEX_START;
144    
145        /** stores the index end marker. */
146        private String indexEnd = DEFAULT_INDEX_END;
147    
148        /**
149         * Sets the attribute end marker.
150         *
151         * @return the attribute end marker
152         */
153        public String getAttributeEnd()
154        {
155            return attributeEnd;
156        }
157    
158        /**
159         * Sets the attribute end marker.
160         *
161         * @param attributeEnd the attribute end marker; can be <b>null</b> if no
162         * end marker is needed
163         */
164        public void setAttributeEnd(String attributeEnd)
165        {
166            this.attributeEnd = attributeEnd;
167        }
168    
169        /**
170         * Returns the attribute start marker.
171         *
172         * @return the attribute start marker
173         */
174        public String getAttributeStart()
175        {
176            return attributeStart;
177        }
178    
179        /**
180         * Sets the attribute start marker. Attribute start and end marker are used
181         * together to detect attributes in a property key.
182         *
183         * @param attributeStart the attribute start marker
184         */
185        public void setAttributeStart(String attributeStart)
186        {
187            this.attributeStart = attributeStart;
188        }
189    
190        /**
191         * Returns the escaped property delimiter string.
192         *
193         * @return the escaped property delimiter
194         */
195        public String getEscapedDelimiter()
196        {
197            return escapedDelimiter;
198        }
199    
200        /**
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         * &quot;.&quot; is used as property delimiter, you can set the escaped
204         * delimiter to &quot;\.&quot; and can then escape the delimiter with a back
205         * slash.
206         *
207         * @param escapedDelimiter the escaped delimiter string
208         */
209        public void setEscapedDelimiter(String escapedDelimiter)
210        {
211            this.escapedDelimiter = escapedDelimiter;
212        }
213    
214        /**
215         * Returns the index end marker.
216         *
217         * @return the index end marker
218         */
219        public String getIndexEnd()
220        {
221            return indexEnd;
222        }
223    
224        /**
225         * Sets the index end marker.
226         *
227         * @param indexEnd the index end marker
228         */
229        public void setIndexEnd(String indexEnd)
230        {
231            this.indexEnd = indexEnd;
232        }
233    
234        /**
235         * Returns the index start marker.
236         *
237         * @return the index start marker
238         */
239        public String getIndexStart()
240        {
241            return indexStart;
242        }
243    
244        /**
245         * Sets the index start marker. Index start and end marker are used together
246         * to detect indices in a property key.
247         *
248         * @param indexStart the index start marker
249         */
250        public void setIndexStart(String indexStart)
251        {
252            this.indexStart = indexStart;
253        }
254    
255        /**
256         * Returns the property delimiter.
257         *
258         * @return the property delimiter
259         */
260        public String getPropertyDelimiter()
261        {
262            return propertyDelimiter;
263        }
264    
265        /**
266         * Sets the property delimiter. This string is used to split the parts of a
267         * property key.
268         *
269         * @param propertyDelimiter the property delimiter
270         */
271        public void setPropertyDelimiter(String propertyDelimiter)
272        {
273            this.propertyDelimiter = propertyDelimiter;
274        }
275    
276        /**
277         * Evaluates the given key and returns all matching nodes. This method
278         * supports the syntax as described in the class comment.
279         *
280         * @param root the root node
281         * @param key the key
282         * @return a list with the matching nodes
283         */
284        public List<ConfigurationNode> query(ConfigurationNode root, String key)
285        {
286            List<ConfigurationNode> nodes = new LinkedList<ConfigurationNode>();
287            findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
288                    root, nodes);
289            return nodes;
290        }
291    
292        /**
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
297         * returned.
298         *
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
302         */
303        public String nodeKey(ConfigurationNode node, String parentKey)
304        {
305            if (parentKey == null)
306            {
307                // this is the root node
308                return StringUtils.EMPTY;
309            }
310    
311            else
312            {
313                DefaultConfigurationKey key = new DefaultConfigurationKey(this,
314                        parentKey);
315                if (node.isAttribute())
316                {
317                    key.appendAttribute(node.getName());
318                }
319                else
320                {
321                    key.append(node.getName(), true);
322                }
323                return key.toString();
324            }
325        }
326    
327        /**
328         * <p>
329         * Prepares Adding the property with the specified key.
330         * </p>
331         * <p>
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
336         * following:
337         * </p>
338         * <p>
339         * <pre>
340         *  tables
341         *     +-- table
342         *             +-- name = user
343         *             +-- fields
344         *                     +-- field
345         *                             +-- name = uid
346         *                     +-- field
347         *                             +-- name = firstName
348         *                     ...
349         *     +-- table
350         *             +-- name = documents
351         *             +-- fields
352         *                    ...
353         * </pre>
354         * </p>
355         * <p>
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
362         * </p>
363         * <p>
364         * <pre>
365         * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
366         * </pre>
367         * </p>
368         * <p>
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.
376         * </p>
377         * <p>
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:
388         * </p>
389         * <p>
390         * <pre>
391         * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
392         * </pre>
393         * </p>
394         * <p>
395         * With this notation it is possible to add new branches everywhere. We
396         * could for instance create a new {@code table} element by
397         * specifying
398         * </p>
399         * <p>
400         * <pre>
401         * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
402         * </pre>
403         * </p>
404         * <p>
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.)
408         * </p>
409         *
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
413         */
414        public NodeAddData prepareAdd(ConfigurationNode root, String key)
415        {
416            DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
417                    this, key).iterator();
418            if (!it.hasNext())
419            {
420                throw new IllegalArgumentException(
421                        "Key for add operation must be defined!");
422            }
423    
424            NodeAddData result = new NodeAddData();
425            result.setParent(findLastPathNode(it, root));
426    
427            while (it.hasNext())
428            {
429                if (!it.isPropertyKey())
430                {
431                    throw new IllegalArgumentException(
432                            "Invalid key for add operation: " + key
433                                    + " (Attribute key in the middle.)");
434                }
435                result.addPathNode(it.currentKey());
436                it.next();
437            }
438    
439            result.setNewNodeName(it.currentKey());
440            result.setAttribute(!it.isPropertyKey());
441            return result;
442        }
443    
444        /**
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.
448         *
449         * @param keyPart the configuration key iterator
450         * @param node the actual node
451         * @param nodes here the found nodes are stored
452         */
453        protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart,
454                ConfigurationNode node, Collection<ConfigurationNode> nodes)
455        {
456            if (!keyPart.hasNext())
457            {
458                nodes.add(node);
459            }
460    
461            else
462            {
463                String key = keyPart.nextKey(false);
464                if (keyPart.isPropertyKey())
465                {
466                    processSubNodes(keyPart, node.getChildren(key), nodes);
467                }
468                if (keyPart.isAttribute())
469                {
470                    processSubNodes(keyPart, node.getAttributes(key), nodes);
471                }
472            }
473        }
474    
475        /**
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.
479         *
480         * @param keyIt the key iterator
481         * @param node the actual node
482         * @return the last existing node on the given path
483         */
484        protected ConfigurationNode findLastPathNode(
485                DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node)
486        {
487            String keyPart = keyIt.nextKey(false);
488    
489            if (keyIt.hasNext())
490            {
491                if (!keyIt.isPropertyKey())
492                {
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!");
497                }
498                int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
499                        .getChildrenCount(keyPart) - 1;
500                if (idx < 0 || idx >= node.getChildrenCount(keyPart))
501                {
502                    return node;
503                }
504                else
505                {
506                    return findLastPathNode(keyIt, (ConfigurationNode) node
507                            .getChildren(keyPart).get(idx));
508                }
509            }
510    
511            else
512            {
513                return node;
514            }
515        }
516    
517        /**
518         * Called by {@code findNodesForKey()} to process the sub nodes of
519         * the current node depending on the type of the current key part (children,
520         * attributes, or both).
521         *
522         * @param keyPart the key part
523         * @param subNodes a list with the sub nodes to process
524         * @param nodes the target collection
525         */
526        private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart,
527                List<ConfigurationNode> subNodes, Collection<ConfigurationNode> nodes)
528        {
529            if (keyPart.hasIndex())
530            {
531                if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
532                {
533                    findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
534                            .clone(), (ConfigurationNode) subNodes.get(keyPart
535                            .getIndex()), nodes);
536                }
537            }
538            else
539            {
540                for (ConfigurationNode node : subNodes)
541                {
542                    findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
543                            .clone(), node, nodes);
544                }
545            }
546        }
547    }