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 &quot;native&quote; 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 (&quot;.&quot;).</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>[@&lt;attributeName&gt;]</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     *  &lt;database&gt;
055     *    &lt;tables&gt;
056     *      &lt;table type=&quot;system&quot;&gt;
057     *        &lt;name&gt;users&lt;/name&gt;
058     *        &lt;fields&gt;
059     *          &lt;field&gt;
060     *            &lt;name&gt;lid&lt;/name&gt;
061     *            &lt;type&gt;long&lt;/name&gt;
062     *          &lt;/field&gt;
063     *          &lt;field&gt;
064     *            &lt;name&gt;usrName&lt;/name&gt;
065     *            &lt;type&gt;java.lang.String&lt;/type&gt;
066     *          &lt;/field&gt;
067     *         ...
068     *        &lt;/fields&gt;
069     *      &lt;/table&gt;
070     *      &lt;table&gt;
071     *        &lt;name&gt;documents&lt;/name&gt;
072     *        &lt;fields&gt;
073     *          &lt;field&gt;
074     *            &lt;name&gt;docid&lt;/name&gt;
075     *            &lt;type&gt;long&lt;/type&gt;
076     *          &lt;/field&gt;
077     *          ...
078     *        &lt;/fields&gt;
079     *      &lt;/table&gt;
080     *      ...
081     *    &lt;/tables&gt;
082     *  &lt;/database&gt;
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    {
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    }