commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ohe...@apache.org
Subject svn commit: r365952 - in /jakarta/commons/proper/configuration/trunk/src: java/org/apache/commons/configuration/tree/ test/org/apache/commons/configuration/tree/
Date Wed, 04 Jan 2006 17:30:47 GMT
Author: oheger
Date: Wed Jan  4 09:30:35 2006
New Revision: 365952

URL: http://svn.apache.org/viewcvs?rev=365952&view=rev
Log:
Default implementation of ExpressionEngine interface, which understands the keys we have used so far, but is much more configurable

Added:
    jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java   (with props)
    jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java   (with props)
    jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java   (with props)
    jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java   (with props)

Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java?rev=365952&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java Wed Jan  4 09:30:35 2006
@@ -0,0 +1,855 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A simple class that supports creation of and iteration on configuration keys
+ * supported by a <code>{@link DefaultExpressionEngine}</code> object.
+ * </p>
+ * <p>
+ * For key creation the class works similar to a StringBuffer: There are several
+ * <code>appendXXXX()</code> methods with which single parts of a key can be
+ * constructed. All these methods return a reference to the actual object so
+ * they can be written in a chain. When using this methods the exact syntax for
+ * keys need not be known.
+ * </p>
+ * <p>
+ * This class also defines a specialized iterator for configuration keys. With
+ * such an iterator a key can be tokenized into its single parts. For each part
+ * it can be checked whether it has an associated index.
+ * </p>
+ * <p>
+ * Instances of this class are always associated with an instance of
+ * <code>{@link DefaultExpressionEngine}</code>, from which the current
+ * delimiters are obtained. So key creation and parsing is specific to this
+ * associated expression engine.
+ * </p>
+ *
+ * @since 1.3
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class DefaultConfigurationKey
+{
+    /** Constant for the initial StringBuffer size. */
+    private static final int INITIAL_SIZE = 32;
+
+    /** Stores a reference to the associated expression engine. */
+    private DefaultExpressionEngine expressionEngine;
+
+    /** Holds a buffer with the so far created key. */
+    private StringBuffer keyBuffer;
+
+    /**
+     * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
+     * the associated expression engine.
+     *
+     * @param engine the expression engine
+     */
+    public DefaultConfigurationKey(DefaultExpressionEngine engine)
+    {
+        keyBuffer = new StringBuffer(INITIAL_SIZE);
+        setExpressionEngine(engine);
+    }
+
+    /**
+     * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
+     * the associated expression engine and an initial key.
+     *
+     * @param engine the expression engine
+     * @param key the key to be wrapped
+     */
+    public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
+    {
+        setExpressionEngine(engine);
+        keyBuffer = new StringBuffer(trim(key));
+    }
+
+    /**
+     * Returns the associated default expression engine.
+     *
+     * @return the associated expression engine
+     */
+    public DefaultExpressionEngine getExpressionEngine()
+    {
+        return expressionEngine;
+    }
+
+    /**
+     * Sets the associated expression engine.
+     *
+     * @param expressionEngine the expression engine (must not be <b>null</b>)
+     */
+    public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
+    {
+        if (expressionEngine == null)
+        {
+            throw new IllegalArgumentException(
+                    "Expression engine must not be null!");
+        }
+        this.expressionEngine = expressionEngine;
+    }
+
+    /**
+     * Appends the name of a property to this key. If necessary, a property
+     * delimiter will be added. If the boolean argument is set to <b>true</b>,
+     * property delimiters contained in the property name will be escaped.
+     *
+     * @param property the name of the property to be added
+     * @param escape a flag if property delimiters in the passed in property name
+     * should be escaped
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey append(String property, boolean escape)
+    {
+        String key;
+        if(escape && property != null)
+        {
+            key = escapeDelimiters(property);
+        }
+        else key = property;
+        key = trim(key);
+
+        if (keyBuffer.length() > 0 && !isAttributeKey(property)
+                && key.length() > 0)
+        {
+            keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
+        }
+
+        keyBuffer.append(key);
+        return this;
+    }
+
+    /**
+     * Appends the name of a property to this key. If necessary, a property
+     * delimiter will be added. Property delimiters in the given string will not
+     * be escaped.
+     *
+     * @param property the name of the property to be added
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey append(String property)
+    {
+        return append(property, false);
+    }
+
+    /**
+     * Appends an index to this configuration key.
+     *
+     * @param index the index to be appended
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey appendIndex(int index)
+    {
+        keyBuffer.append(getExpressionEngine().getIndexStart());
+        keyBuffer.append(index);
+        keyBuffer.append(getExpressionEngine().getIndexEnd());
+        return this;
+    }
+
+    /**
+     * Appends an attribute to this configuration key.
+     *
+     * @param attr the name of the attribute to be appended
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey appendAttribute(String attr)
+    {
+        keyBuffer.append(constructAttributeKey(attr));
+        return this;
+    }
+
+    /**
+     * Returns the actual length of this configuration key.
+     *
+     * @return the length of this key
+     */
+    public int length()
+    {
+        return keyBuffer.length();
+    }
+
+    /**
+     * Sets the new length of this configuration key. With this method it is
+     * possible to truncate the key, e.g. to return to a state prior calling
+     * some <code>append()</code> methods. The semantic is the same as the
+     * <code>setLength()</code> method of <code>StringBuffer</code>.
+     *
+     * @param len the new length of the key
+     */
+    public void setLength(int len)
+    {
+        keyBuffer.setLength(len);
+    }
+
+    /**
+     * Checks if two <code>ConfigurationKey</code> objects are equal. The
+     * method can be called with strings or other objects, too.
+     *
+     * @param c the object to compare
+     * @return a flag if both objects are equal
+     */
+    public boolean equals(Object c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        return keyBuffer.toString().equals(c.toString());
+    }
+
+    /**
+     * Returns the hash code for this object.
+     *
+     * @return the hash code
+     */
+    public int hashCode()
+    {
+        return String.valueOf(keyBuffer).hashCode();
+    }
+
+    /**
+     * Returns a string representation of this object. This is the configuration
+     * key as a plain string.
+     *
+     * @return a string for this object
+     */
+    public String toString()
+    {
+        return keyBuffer.toString();
+    }
+
+    /**
+     * Tests if the specified key represents an attribute according to the
+     * current expression engine.
+     *
+     * @param key the key to be checked
+     * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
+     */
+    public boolean isAttributeKey(String key)
+    {
+        if (key == null)
+        {
+            return false;
+        }
+
+        return key.startsWith(getExpressionEngine().getAttributeStart())
+                && (getExpressionEngine().getAttributeEnd() == null || key
+                        .endsWith(getExpressionEngine().getAttributeEnd()));
+    }
+
+    /**
+     * Decorates the given key so that it represents an attribute. Adds special
+     * start and end markers. The passed in string will be modified only if does
+     * not already represent an attribute.
+     *
+     * @param key the key to be decorated
+     * @return the decorated attribute key
+     */
+    public String constructAttributeKey(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        if (isAttributeKey(key))
+        {
+            return key;
+        }
+        else
+        {
+            StringBuffer buf = new StringBuffer();
+            buf.append(getExpressionEngine().getAttributeStart()).append(key);
+            if (getExpressionEngine().getAttributeEnd() != null)
+            {
+                buf.append(getExpressionEngine().getAttributeEnd());
+            }
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Extracts the name of the attribute from the given attribute key. This
+     * method removes the attribute markers - if any - from the specified key.
+     *
+     * @param key the attribute key
+     * @return the name of the corresponding attribute
+     */
+    public String attributeName(String key)
+    {
+        return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
+    }
+
+    /**
+     * Removes leading property delimiters from the specified key.
+     *
+     * @param key the key
+     * @return the key with removed leading property delimiters
+     */
+    public String trimLeft(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        else
+        {
+            String result = key;
+            while (hasLeadingDelimiter(result))
+            {
+                result = result.substring(getExpressionEngine()
+                        .getPropertyDelimiter().length());
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Removes trailing property delimiters from the specified key.
+     *
+     * @param key the key
+     * @return the key with removed trailing property delimiters
+     */
+    public String trimRight(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        else
+        {
+            String result = key;
+            while (hasTrailingDelimiter(result))
+            {
+                result = result
+                        .substring(0, result.length()
+                                - getExpressionEngine().getPropertyDelimiter()
+                                        .length());
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Removes delimiters at the beginning and the end of the specified key.
+     *
+     * @param key the key
+     * @return the key with removed property delimiters
+     */
+    public String trim(String key)
+    {
+        return trimRight(trimLeft(key));
+    }
+
+    /**
+     * Returns an iterator for iterating over the single components of this
+     * configuration key.
+     *
+     * @return an iterator for this key
+     */
+    public KeyIterator iterator()
+    {
+        return new KeyIterator();
+    }
+
+    /**
+     * Helper method that checks if the specified key ends with a property
+     * delimiter.
+     *
+     * @param key the key to check
+     * @return a flag if there is a trailing delimiter
+     */
+    private boolean hasTrailingDelimiter(String key)
+    {
+        return key.endsWith(getExpressionEngine().getPropertyDelimiter())
+                && (getExpressionEngine().getEscapedDelimiter() == null || !key
+                        .endsWith(getExpressionEngine().getEscapedDelimiter()));
+    }
+
+    /**
+     * Helper method that checks if the specified key starts with a property
+     * delimiter.
+     *
+     * @param key the key to check
+     * @return a flag if there is a leading delimiter
+     */
+    private boolean hasLeadingDelimiter(String key)
+    {
+        return key.startsWith(getExpressionEngine().getPropertyDelimiter())
+                && (getExpressionEngine().getEscapedDelimiter() == null || !key
+                        .startsWith(getExpressionEngine().getEscapedDelimiter()));
+    }
+
+    /**
+     * Helper method for removing attribute markers from a key.
+     *
+     * @param key the key
+     * @return the key with removed attribute markers
+     */
+    private String removeAttributeMarkers(String key)
+    {
+        return key
+                .substring(
+                        getExpressionEngine().getAttributeStart().length(),
+                        key.length()
+                                - ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
+                                        .getAttributeEnd().length()
+                                        : 0));
+    }
+
+    /**
+     * Unescapes the delimiters in the specified string.
+     *
+     * @param key the key to be unescaped
+     * @return the unescaped key
+     */
+    private String unescapeDelimiters(String key)
+    {
+        return (getExpressionEngine().getEscapedDelimiter() == null) ? key
+                : StringUtils.replace(key, getExpressionEngine()
+                        .getEscapedDelimiter(), getExpressionEngine()
+                        .getPropertyDelimiter());
+    }
+
+    /**
+     * Escapes the delimiters in the specified string.
+     * @param key the key to be escaped
+     * @return the escaped key
+     */
+    private String escapeDelimiters(String key)
+    {
+        return (getExpressionEngine().getEscapedDelimiter() == null || key.indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key : StringUtils.replace(key, getExpressionEngine().getPropertyDelimiter(), getExpressionEngine().getEscapedDelimiter());
+    }
+
+    /**
+     * A specialized iterator class for tokenizing a configuration key. This
+     * class implements the normal iterator interface. In addition it provides
+     * some specific methods for configuration keys.
+     */
+    public class KeyIterator implements Iterator, Cloneable
+    {
+        /** Stores the current key name. */
+        private String current;
+
+        /** Stores the start index of the actual token. */
+        private int startIndex;
+
+        /** Stores the end index of the actual token. */
+        private int endIndex;
+
+        /** Stores the index of the actual property if there is one. */
+        private int indexValue;
+
+        /** Stores a flag if the actual property has an index. */
+        private boolean hasIndex;
+
+        /** Stores a flag if the actual property is an attribute. */
+        private boolean attribute;
+
+        /**
+         * Returns the next key part of this configuration key. This is a short
+         * form of <code>nextKey(false)</code>.
+         *
+         * @return the next key part
+         */
+        public String nextKey()
+        {
+            return nextKey(false);
+        }
+
+        /**
+         * Returns the next key part of this configuration key. The boolean
+         * parameter indicates wheter a decorated key should be returned. This
+         * affects only attribute keys: if the parameter is <b>false</b>, the
+         * attribute markers are stripped from the key; if it is <b>true</b>,
+         * they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the next key part
+         */
+        public String nextKey(boolean decorated)
+        {
+            if (!hasNext())
+            {
+                throw new NoSuchElementException("No more key parts!");
+            }
+
+            hasIndex = false;
+            indexValue = -1;
+            String key = findNextIndices();
+
+            current = key;
+            hasIndex = checkIndex(key);
+            attribute = checkAttribute(current);
+
+            return currentKey(decorated);
+        }
+
+        /**
+         * Checks if there is a next element.
+         *
+         * @return a flag if there is a next element
+         */
+        public boolean hasNext()
+        {
+            return endIndex < keyBuffer.length();
+        }
+
+        /**
+         * Returns the next object in the iteration.
+         *
+         * @return the next object
+         */
+        public Object next()
+        {
+            return nextKey();
+        }
+
+        /**
+         * Removes the current object in the iteration. This method is not
+         * supported by this iterator type, so an exception is thrown.
+         */
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Remove not supported!");
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). This is the same key the previous <code>next()</code>
+         * call had returned. (Short form of <code>currentKey(false)</code>.
+         *
+         * @return the current key
+         */
+        public String currentKey()
+        {
+            return currentKey(false);
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). The boolean parameter indicates wheter a decorated key
+         * should be returned. This affects only attribute keys: if the
+         * parameter is <b>false</b>, the attribute markers are stripped from
+         * the key; if it is <b>true</b>, they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the current key
+         */
+        public String currentKey(boolean decorated)
+        {
+            return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
+                    : current;
+        }
+
+        /**
+         * Returns a flag if the current key is an attribute. This method can be
+         * called after <code>next()</code>.
+         *
+         * @return a flag if the current key is an attribute
+         */
+        public boolean isAttribute()
+        {
+            // if attribute emulation mode is active, the last part of a key is
+            // always an attribute key, too
+            return attribute || (isAttributeEmulatingMode() && !hasNext());
+        }
+
+        /**
+         * Returns a flag whether the current key refers to a property (i.e. is
+         * no special attribute key). Usually this method will return the
+         * opposite of <code>isAttribute()</code>, but if the delimiters for
+         * normal properties and attributes are set to the same string, it is
+         * possible that both methods return <b>true</b>.
+         *
+         * @return a flag if the current key is a property key
+         * @see #isAttribute()
+         */
+        public boolean isPropertyKey()
+        {
+            return !attribute;
+        }
+
+        /**
+         * Returns the index value of the current key. If the current key does
+         * not have an index, return value is -1. This method can be called
+         * after <code>next()</code>.
+         *
+         * @return the index value of the current key
+         */
+        public int getIndex()
+        {
+            return indexValue;
+        }
+
+        /**
+         * Returns a flag if the current key has an associated index. This
+         * method can be called after <code>next()</code>.
+         *
+         * @return a flag if the current key has an index
+         */
+        public boolean hasIndex()
+        {
+            return hasIndex;
+        }
+
+        /**
+         * Creates a clone of this object.
+         *
+         * @return a clone of this object
+         */
+        public Object clone()
+        {
+            try
+            {
+                return super.clone();
+            }
+            catch (CloneNotSupportedException cex)
+            {
+                // should not happen
+                return null;
+            }
+        }
+
+        /**
+         * Helper method for determining the next indices.
+         *
+         * @return the next key part
+         */
+        private String findNextIndices()
+        {
+            startIndex = endIndex;
+            // skip empty names
+            while (startIndex < length()
+                    && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
+            {
+                startIndex += getExpressionEngine().getPropertyDelimiter()
+                        .length();
+            }
+
+            // Key ends with a delimiter?
+            if (startIndex >= length())
+            {
+                endIndex = length();
+                startIndex = endIndex - 1;
+                return keyBuffer.substring(startIndex, endIndex);
+            }
+            else
+            {
+                return nextKeyPart();
+            }
+        }
+
+        /**
+         * Helper method for extracting the next key part. Takes escaping of
+         * delimiter characters into account.
+         *
+         * @return the next key part
+         */
+        private String nextKeyPart()
+        {
+            int attrIdx = keyBuffer.toString().indexOf(
+                    getExpressionEngine().getAttributeStart(), startIndex);
+            if (attrIdx < 0 || attrIdx == startIndex)
+            {
+                attrIdx = length();
+            }
+
+            int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
+                    attrIdx);
+            if (delIdx < 0)
+            {
+                delIdx = attrIdx;
+            }
+
+            endIndex = Math.min(attrIdx, delIdx);
+            return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
+        }
+
+        /**
+         * Searches the next unescaped delimiter from the given position.
+         *
+         * @param key the key
+         * @param pos the start position
+         * @param endPos the end position
+         * @return the position of the next delimiter or -1 if there is none
+         */
+        private int nextDelimiterPos(String key, int pos, int endPos)
+        {
+            int delimiterPos = pos;
+            boolean found = false;
+
+            do
+            {
+                delimiterPos = key.indexOf(getExpressionEngine()
+                        .getPropertyDelimiter(), delimiterPos);
+                if (delimiterPos < 0 || delimiterPos >= endPos)
+                {
+                    return -1;
+                }
+                int escapePos = escapedPosition(key, delimiterPos);
+                if (escapePos < 0)
+                {
+                    found = true;
+                }
+                else
+                {
+                    delimiterPos = escapePos;
+                }
+            }
+            while (!found);
+
+            return delimiterPos;
+        }
+
+        /**
+         * Checks if a delimiter at the specified position is escaped. If this
+         * is the case, the next valid search position will be returned.
+         * Otherwise the return value is -1.
+         *
+         * @param key the key to check
+         * @param pos the position where a delimiter was found
+         * @return information about escaped delimiters
+         */
+        private int escapedPosition(String key, int pos)
+        {
+            if (getExpressionEngine().getEscapedDelimiter() == null)
+            {
+                // nothing to escape
+                return -1;
+            }
+            int escapeOffset = escapeOffset();
+            if (escapeOffset < 0 || escapeOffset > pos)
+            {
+                // No escaping possible at this position
+                return -1;
+            }
+
+            int escapePos = key.indexOf(getExpressionEngine()
+                    .getEscapedDelimiter(), pos - escapeOffset);
+            if (escapePos <= pos && escapePos >= 0)
+            {
+                // The found delimiter is escaped. Next valid search position
+                // is behind the escaped delimiter.
+                return escapePos
+                        + getExpressionEngine().getEscapedDelimiter().length();
+            }
+            else
+            {
+                return -1;
+            }
+        }
+
+        /**
+         * Determines the relative offset of an escaped delimiter in relation to
+         * a delimiter. Depending on the used delimiter and escaped delimiter
+         * tokens the position where to search for an escaped delimiter is
+         * different. If, for instance, the dot character (&quot;.&quot;) is
+         * used as delimiter, and a doubled dot (&quot;..&quot;) as escaped
+         * delimiter, the escaped delimiter starts at the same position as the
+         * delimiter. If the token &quot;\.&quot; was used, it would start one
+         * character before the delimiter because the delimiter character
+         * &quot;.&quot; is the second character in the escaped delimiter
+         * string. This relation will be determined by this method. For this to
+         * work the delimiter string must be contained in the escaped delimiter
+         * string.
+         *
+         * @return the relative offset of the escaped delimiter in relation to a
+         * delimiter
+         */
+        private int escapeOffset()
+        {
+            return getExpressionEngine().getEscapedDelimiter().indexOf(
+                    getExpressionEngine().getPropertyDelimiter());
+        }
+
+        /**
+         * Helper method for checking if the passed key is an attribute. If this
+         * is the case, the internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if the key is an attribute
+         */
+        private boolean checkAttribute(String key)
+        {
+            if (isAttributeKey(key))
+            {
+                current = removeAttributeMarkers(key);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /**
+         * Helper method for checking if the passed key contains an index. If
+         * this is the case, internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if an index is defined
+         */
+        private boolean checkIndex(String key)
+        {
+            boolean result = false;
+
+            int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
+            if (idx > 0)
+            {
+                int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
+                        idx);
+
+                if (endidx > idx + 1)
+                {
+                    indexValue = Integer.parseInt(key
+                            .substring(idx + 1, endidx));
+                    current = key.substring(0, idx);
+                    result = true;
+                }
+            }
+
+            return result;
+        }
+
+        /**
+         * Returns a flag whether attributes are marked the same way as normal
+         * property keys. We call this the &quot;attribute emulating mode&quot;.
+         * When navigating through node hierarchies it might be convenient to
+         * treat attributes the same way than other child nodes, so an
+         * expression engine supports to set the attribute markers to the same
+         * value than the property delimiter. If this is the case, some special
+         * checks have to be performed.
+         *
+         * @return a flag if attributes and normal property keys are treated the
+         * same way
+         */
+        private boolean isAttributeEmulatingMode()
+        {
+            return getExpressionEngine().getAttributeEnd() == null
+                    && StringUtils.equals(getExpressionEngine()
+                            .getPropertyDelimiter(), getExpressionEngine()
+                            .getAttributeStart());
+        }
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java?rev=365952&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java Wed Jan  4 09:30:35 2006
@@ -0,0 +1,545 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A default implementation of the <code>ExpressionEngine</code> interface
+ * providing the &quot;native&quote; expression language for hierarchical
+ * configurations.
+ * </p>
+ * <p>
+ * This class implements a rather simple expression language for navigating
+ * through a hierarchy of configuration nodes. It supports the following
+ * operations:
+ * </p>
+ * <p>
+ * <ul>
+ * <li>Navigating from a node to one of its children using the child node
+ * delimiter, which is by the default a dot (&quot;.&quot;).</li>
+ * <li>Navigating from a node to one of its attributes using the attribute node
+ * delimiter, which by default follows the XPATH like syntax
+ * <code>[@&lt;attributeName&gt;]</code>.</li>
+ * <li>If there are multiple child or attribute nodes with the same name, a
+ * specific node can be selected using a numerical index. By default indices are
+ * written in paranthesis.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * As an example consider the following XML document:
+ * </p>
+ *
+ * <pre>
+ *  &lt;database&gt;
+ *    &lt;tables&gt;
+ *      &lt;table type=&quot;system&quot;&gt;
+ *        &lt;name&gt;users&lt;/name&gt;
+ *        &lt;fields&gt;
+ *          &lt;field&gt;
+ *            &lt;name&gt;lid&lt;/name&gt;
+ *            &lt;type&gt;long&lt;/name&gt;
+ *          &lt;/field&gt;
+ *          &lt;field&gt;
+ *            &lt;name&gt;usrName&lt;/name&gt;
+ *            &lt;type&gt;java.lang.String&lt;/type&gt;
+ *          &lt;/field&gt;
+ *         ...
+ *        &lt;/fields&gt;
+ *      &lt;/table&gt;
+ *      &lt;table&gt;
+ *        &lt;name&gt;documents&lt;/name&gt;
+ *        &lt;fields&gt;
+ *          &lt;field&gt;
+ *            &lt;name&gt;docid&lt;/name&gt;
+ *            &lt;type&gt;long&lt;/type&gt;
+ *          &lt;/field&gt;
+ *          ...
+ *        &lt;/fields&gt;
+ *      &lt;/table&gt;
+ *      ...
+ *    &lt;/tables&gt;
+ *  &lt;/database&gt;
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * If this document is parsed and stored in a hierarchical configuration object,
+ * for instance the key <code>tables.table(0).name</code> can be used to find
+ * out the name of the first table. In opposite <code>tables.table.name</code>
+ * would return a collection with the names of all available tables. Similarily
+ * the key <code>tables.table(1).fields.field.name</code> returns a collection
+ * with the names of all fields of the second table. If another index is added
+ * after the <code>field</code> element, a single field can be accessed:
+ * <code>tables.table(1).fields.field(0).name</code>. The key
+ * <code>tables.table(0)[@type]</code> would select the type attribute of the
+ * first table.
+ * </p>
+ * <p>
+ * This example works with the default values for delimiters and index markers.
+ * It is also possible to set custom values for these properties so that you can
+ * adapt a <code>DefaultExpressionEngine</code> to your personal needs.
+ * </p>
+ *
+ * @since 1.3
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class DefaultExpressionEngine implements ExpressionEngine
+{
+    /** Constant for the default property delimiter. */
+    public static final String DEFAULT_PROPERTY_DELIMITER = ".";
+
+    /** Constant for the default escaped property delimiter. */
+    public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
+            + DEFAULT_PROPERTY_DELIMITER;
+
+    /** Constant for the default attribute start marker. */
+    public static final String DEFAULT_ATTRIBUTE_START = "[@";
+
+    /** Constant for the default attribute end marker. */
+    public static final String DEFAULT_ATTRIBUTE_END = "]";
+
+    /** Constant for the default index start marker. */
+    public static final String DEFAULT_INDEX_START = "(";
+
+    /** Constant for the default index end marker. */
+    public static final String DEFAULT_INDEX_END = ")";
+
+    /** Stores the property delimiter. */
+    private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
+
+    /** Stores the escaped property delimiter. */
+    private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
+
+    /** Stores the attribute start marker. */
+    private String attributeStart = DEFAULT_ATTRIBUTE_START;
+
+    /** Stores the attribute end marker. */
+    private String attributeEnd = DEFAULT_ATTRIBUTE_END;
+
+    /** Stores the index start marker. */
+    private String indexStart = DEFAULT_INDEX_START;
+
+    /** stores the index end marker. */
+    private String indexEnd = DEFAULT_INDEX_END;
+
+    /**
+     * Sets the attribute end marker.
+     *
+     * @return the attribute end marker
+     */
+    public String getAttributeEnd()
+    {
+        return attributeEnd;
+    }
+
+    /**
+     * Sets the attribute end marker.
+     *
+     * @param attributeEnd the attribute end marker; can be <b>null</b> if no
+     * end marker is needed
+     */
+    public void setAttributeEnd(String attributeEnd)
+    {
+        this.attributeEnd = attributeEnd;
+    }
+
+    /**
+     * Returns the attribute start marker.
+     *
+     * @return the attribute start marker
+     */
+    public String getAttributeStart()
+    {
+        return attributeStart;
+    }
+
+    /**
+     * Sets the attribute start marker. Attribute start and end marker are used
+     * together to detect attributes in a property key.
+     *
+     * @param attributeStart the attribute start marker
+     */
+    public void setAttributeStart(String attributeStart)
+    {
+        this.attributeStart = attributeStart;
+    }
+
+    /**
+     * Returns the escaped property delimiter string.
+     *
+     * @return the escaped property delimiter
+     */
+    public String getEscapedDelimiter()
+    {
+        return escapedDelimiter;
+    }
+
+    /**
+     * Sets the escaped property delimiter string. With this string a delimiter
+     * that belongs to the key of a property can be escaped. If for instance
+     * &quot;.&quot; is used as property delimiter, you can set the escaped
+     * delimiter to &quot;\.&quot; and can then escape the delimiter with a back
+     * slash.
+     *
+     * @param escapedDelimiter the escaped delimiter string
+     */
+    public void setEscapedDelimiter(String escapedDelimiter)
+    {
+        this.escapedDelimiter = escapedDelimiter;
+    }
+
+    /**
+     * Returns the index end marker.
+     *
+     * @return the index end marker
+     */
+    public String getIndexEnd()
+    {
+        return indexEnd;
+    }
+
+    /**
+     * Sets the index end marker.
+     *
+     * @param indexEnd the index end marker
+     */
+    public void setIndexEnd(String indexEnd)
+    {
+        this.indexEnd = indexEnd;
+    }
+
+    /**
+     * Returns the index start marker.
+     *
+     * @return the index start marker
+     */
+    public String getIndexStart()
+    {
+        return indexStart;
+    }
+
+    /**
+     * Sets the index start marker. Index start and end marker are used together
+     * to detect indices in a property key.
+     *
+     * @param indexStart the index start marker
+     */
+    public void setIndexStart(String indexStart)
+    {
+        this.indexStart = indexStart;
+    }
+
+    /**
+     * Returns the property delimiter.
+     *
+     * @return the property delimiter
+     */
+    public String getPropertyDelimiter()
+    {
+        return propertyDelimiter;
+    }
+
+    /**
+     * Sets the property delmiter. This string is used to split the parts of a
+     * property key.
+     *
+     * @param propertyDelimiter the property delimiter
+     */
+    public void setPropertyDelimiter(String propertyDelimiter)
+    {
+        this.propertyDelimiter = propertyDelimiter;
+    }
+
+    /**
+     * Evaluates the given key and returns all matching nodes. This method
+     * supports the syntax as described in the class comment.
+     *
+     * @param root the root node
+     * @param key the key
+     * @return a list with the matching nodes
+     */
+    public List query(ConfigurationNode root, String key)
+    {
+        List nodes = new LinkedList();
+        findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
+                root, nodes);
+        return nodes;
+    }
+
+    /**
+     * Determines the key of the passed in node. This implementation takes the
+     * given parent key, adds a property delimiter, and then adds the node's
+     * name. (For attribute nodes the attribute delimiters are used instead.)
+     * The name of the root node is a blanc string. Note that no indices will be
+     * returned.
+     *
+     * @param node the node whose key is to be determined
+     * @param parentKey the key of this node's parent
+     * @return the key for the given node
+     */
+    public String nodeKey(ConfigurationNode node, String parentKey)
+    {
+        if (parentKey == null)
+        {
+            // this is the root node
+            return StringUtils.EMPTY;
+        }
+
+        else
+        {
+            DefaultConfigurationKey key = new DefaultConfigurationKey(this,
+                    parentKey);
+            if (node.isAttribute())
+            {
+                key.appendAttribute(node.getName());
+            }
+            else
+            {
+                key.append(node.getName(), true);
+            }
+            return key.toString();
+        }
+    }
+
+    /**
+     * <p>
+     * Prepares Adding the property with the specified key.
+     * </p>
+     * <p>
+     * To be able to deal with the structure supported by hierarchical
+     * configuration implementations the passed in key is of importance,
+     * especially the indices it might contain. The following example should
+     * clearify this: Suppose the actual node structure looks like the
+     * following:
+     * </p>
+     * <p>
+     * <pre>
+     *  tables
+     *     +-- table
+     *             +-- name = user
+     *             +-- fields
+     *                     +-- field
+     *                             +-- name = uid
+     *                     +-- field
+     *                             +-- name = firstName
+     *                     ...
+     *     +-- table
+     *             +-- name = documents
+     *             +-- fields
+     *                    ...
+     * </pre>
+     * </p>
+     * <p>
+     * In this example a database structure is defined, e.g. all fields of the
+     * first table could be accessed using the key
+     * <code>tables.table(0).fields.field.name</code>. If now properties are
+     * to be added, it must be exactly specified at which position in the
+     * hierarchy the new property is to be inserted. So to add a new field name
+     * to a table it is not enough to say just
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);
+     * </pre>
+     * </p>
+     * <p>
+     * The statement given above contains some ambiguity. For instance it is not
+     * clear, to which table the new field should be added. If this method finds
+     * such an ambiguity, it is resolved by following the last valid path. Here
+     * this would be the last table. The same is true for the <code>field</code>;
+     * because there are multiple fields and no explicit index is provided, a
+     * new <code>name</code> property would be added to the last field - which
+     * is propably not what was desired.
+     * </p>
+     * <p>
+     * To make things clear explicit indices should be provided whenever
+     * possible. In the example above the exact table could be specified by
+     * providing an index for the <code>table</code> element as in
+     * <code>tables.table(1).fields</code>. By specifying an index it can
+     * also be expressed that at a given position in the configuration tree a
+     * new branch should be added. In the example above we did not want to add
+     * an additional <code>name</code> element to the last field of the table,
+     * but we want a complete new <code>field</code> element. This can be
+     * achieved by specifying an invalid index (like -1) after the element where
+     * a new branch should be created. Given this our example would run:
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);
+     * </pre>
+     * </p>
+     * <p>
+     * With this notation it is possible to add new branches everywhere. We
+     * could for instance create a new <code>table</code> element by
+     * specifying
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);
+     * </pre>
+     * </p>
+     * <p>
+     * (Note that because after the <code>table</code> element a new branch is
+     * created indices in following elements are not relevant; the branch is new
+     * so there cannot be any ambiguities.)
+     * </p>
+     *
+     * @param root the root node of the nodes hierarchy
+     * @param key the key of the new property
+     * @return a data object with information needed for the add operation
+     */
+    public NodeAddData prepareAdd(ConfigurationNode root, String key)
+    {
+        DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
+                this, key).iterator();
+        if (!it.hasNext())
+        {
+            throw new IllegalArgumentException(
+                    "Key for add operation must be defined!");
+        }
+
+        NodeAddData result = new NodeAddData();
+        result.setParent(findLastPathNode(it, root));
+
+        while (it.hasNext())
+        {
+            if (!it.isPropertyKey())
+            {
+                throw new IllegalArgumentException(
+                        "Invalid key for add operation: " + key
+                                + " (Attribute key in the middle.)");
+            }
+            result.addPathNode(it.currentKey());
+            it.next();
+        }
+
+        result.setNewNodeName(it.currentKey());
+        result.setAttribute(!it.isPropertyKey());
+        return result;
+    }
+
+    /**
+     * Recursive helper method for evaluating a key. This method processes all
+     * facets of a configuration key, traverses the tree of properties and
+     * fetches the the nodes of all matching properties.
+     *
+     * @param keyPart the configuration key iterator
+     * @param node the actual node
+     * @param nodes here the found nodes are stored
+     */
+    protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart,
+            ConfigurationNode node, Collection nodes)
+    {
+        if (!keyPart.hasNext())
+        {
+            nodes.add(node);
+        }
+
+        else
+        {
+            String key = keyPart.nextKey(false);
+            if (keyPart.isPropertyKey())
+            {
+                processSubNodes(keyPart, node.getChildren(key), nodes);
+            }
+            if (keyPart.isAttribute())
+            {
+                processSubNodes(keyPart, node.getAttributes(key), nodes);
+            }
+        }
+    }
+
+    /**
+     * Finds the last existing node for an add operation. This method traverses
+     * the configuration node tree along the specified key. The last existing
+     * node on this path is returned.
+     *
+     * @param keyIt the key iterator
+     * @param node the actual node
+     * @return the last existing node on the given path
+     */
+    protected ConfigurationNode findLastPathNode(
+            DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node)
+    {
+        String keyPart = keyIt.nextKey(false);
+
+        if (keyIt.hasNext())
+        {
+            if (!keyIt.isPropertyKey())
+            {
+                // Attribute keys can only appear as last elements of the path
+                throw new IllegalArgumentException(
+                        "Invalid path for add operation: "
+                                + "Attribute key in the middle!");
+            }
+            int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
+                    .getChildrenCount(keyPart) - 1;
+            if (idx < 0 || idx >= node.getChildrenCount(keyPart))
+            {
+                return node;
+            }
+            else
+            {
+                return findLastPathNode(keyIt, (ConfigurationNode) node
+                        .getChildren(keyPart).get(idx));
+            }
+        }
+
+        else
+        {
+            return node;
+        }
+    }
+
+    /**
+     * Called by <code>findNodesForKey()</code> to process the sub nodes of
+     * the current node depending on the type of the current key part (children,
+     * attributes, or both).
+     *
+     * @param keyPart the key part
+     * @param subNodes a list with the sub nodes to process
+     * @param nodes the target collection
+     */
+    private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart,
+            List subNodes, Collection nodes)
+    {
+        if (keyPart.hasIndex())
+        {
+            if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
+            {
+                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
+                        .clone(), (ConfigurationNode) subNodes.get(keyPart
+                        .getIndex()), nodes);
+            }
+        }
+        else
+        {
+            for (Iterator it = subNodes.iterator(); it.hasNext();)
+            {
+                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
+                        .clone(), (ConfigurationNode) it.next(), nodes);
+            }
+        }
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java?rev=365952&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java Wed Jan  4 09:30:35 2006
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.NoSuchElementException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for DefaultConfigurationKey.
+ *
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class TestDefaultConfigurationKey extends TestCase
+{
+    /** Constant for a test key. */
+    private static final String TESTPROPS = "tables.table(0).fields.field(1)";
+
+    /** Constant for a test attribute key. */
+    private static final String TESTATTR = "[@dataType]";
+
+    /** Constant for a complex attribute key. */
+    private static final String TESTKEY = TESTPROPS + TESTATTR;
+
+    /** Stores the expression engine of the key to test. */
+    DefaultExpressionEngine expressionEngine;
+
+    /** Stores the object to be tested. */
+    DefaultConfigurationKey key;
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        expressionEngine = new DefaultExpressionEngine();
+        key = new DefaultConfigurationKey(expressionEngine);
+    }
+
+    /**
+     * Tests setting the expression engine to null. This should not be allowed.
+     */
+    public void testSetNullExpressionEngine()
+    {
+        try
+        {
+            key.setExpressionEngine(null);
+            fail("Could set null expression engine!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests the isAttributeKey() method with several keys.
+     */
+    public void testIsAttributeKey()
+    {
+        assertTrue("Attribute key not detected", key.isAttributeKey(TESTATTR));
+        assertFalse("Property key considered as attribute", key
+                .isAttributeKey(TESTPROPS));
+        assertFalse("Null key considered as attribute", key
+                .isAttributeKey(null));
+    }
+
+    /**
+     * Tests if attribute keys are correctly detected if no end markers are set.
+     * (In this test case we use the same delimiter for attributes as for simple
+     * properties.)
+     */
+    public void testIsAttributeKeyWithoutEndMarkers()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine
+                .setAttributeStart(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER);
+        assertTrue(
+                "Attribute key not detected",
+                key
+                        .isAttributeKey(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER
+                                + "test"));
+        assertFalse("Property key considered as attribute key", key
+                .isAttributeKey(TESTATTR));
+    }
+
+    /**
+     * Tests removing leading delimiters.
+     */
+    public void testTrimLeft()
+    {
+        assertEquals("Key was not left trimmed", "test.", key
+                .trimLeft(".test."));
+        assertEquals("Too much left trimming", "..test.", key
+                .trimLeft("..test."));
+    }
+
+    /**
+     * Tests removing trailing delimiters.
+     */
+    public void testTrimRight()
+    {
+        assertEquals("Key was not right trimmed", ".test", key
+                .trimRight(".test."));
+        assertEquals("Too much right trimming", ".test..", key
+                .trimRight(".test.."));
+    }
+
+    /**
+     * Tests removing delimiters.
+     */
+    public void testTrim()
+    {
+        assertEquals("Key was not trimmed", "test", key.trim(".test."));
+        assertEquals("Null key could not be processed", "", key.trim(null));
+        assertEquals("Delimiter could not be processed", "", key
+                .trim(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER));
+    }
+
+    /**
+     * Tests appending keys.
+     */
+    public void testAppend()
+    {
+        key.append("tables").append("table(0).");
+        key.append("fields.").append("field(1)");
+        key.append(null).append(TESTATTR);
+        assertEquals("Wrong key", TESTKEY, key.toString());
+    }
+
+    /**
+     * Tests appending keys that contain delimiters.
+     */
+    public void testAppendDelimiters()
+    {
+        key.append("key..").append("test").append(".");
+        key.append(".more").append("..tests");
+        assertEquals("Wrong key", "key...test.more...tests", key.toString());
+    }
+
+    /**
+     * Tests appending keys that contain delimiters when no escpaped delimiter
+     * is defined.
+     */
+    public void testAppendDelimitersWithoutEscaping()
+    {
+        expressionEngine.setEscapedDelimiter(null);
+        key.append("key.......").append("test").append(".");
+        key.append(".more").append("..tests");
+        assertEquals("Wrong constructed key", "key.test.more.tests", key
+                .toString());
+    }
+
+    /**
+     * Tests calling append with the escape flag.
+     */
+    public void testAppendWithEscapeFlag()
+    {
+        key.append(".key.test.", true);
+        key.append(".more").append(".tests", true);
+        assertEquals("Wrong constructed key", "..key..test...more...tests", key
+                .toString());
+    }
+
+    /**
+     * Tests constructing keys for attributes.
+     */
+    public void testConstructAttributeKey()
+    {
+        assertEquals("Wrong attribute key", TESTATTR, key
+                .constructAttributeKey("dataType"));
+        assertEquals("Attribute key was incorrectly converted", TESTATTR, key
+                .constructAttributeKey(TESTATTR));
+        assertEquals("Null key could not be processed", "", key
+                .constructAttributeKey(null));
+    }
+
+    /**
+     * Tests constructing attribute keys when no end markers are defined. In
+     * this test case we use the property delimiter as attribute prefix.
+     */
+    public void testConstructAttributeKeyWithoutEndMarkers()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine.setAttributeStart(expressionEngine
+                .getPropertyDelimiter());
+        assertEquals("Wrong attribute key", ".test", key
+                .constructAttributeKey("test"));
+        assertEquals("Attribute key was incorrectly converted", ".test", key
+                .constructAttributeKey(".test"));
+    }
+
+    /**
+     * Tests appending attribute keys.
+     */
+    public void testAppendAttribute()
+    {
+        key.appendAttribute("dataType");
+        assertEquals("Attribute key not correctly appended", TESTATTR, key
+                .toString());
+    }
+
+    /**
+     * Tests appending an attribute key that is already decorated-
+     */
+    public void testAppendDecoratedAttributeKey()
+    {
+        key.appendAttribute(TESTATTR);
+        assertEquals("Decorated attribute key not correctly appended",
+                TESTATTR, key.toString());
+    }
+
+    /**
+     * Tests appending a null attribute key.
+     */
+    public void testAppendNullAttributeKey()
+    {
+        key.appendAttribute(null);
+        assertEquals("Null attribute key not correctly appended", "", key
+                .toString());
+    }
+
+    /**
+     * Tests appending an index to a key.
+     */
+    public void testAppendIndex()
+    {
+        key.append("test").appendIndex(42);
+        assertEquals("Index was not correctly appended", "test(42)", key
+                .toString());
+    }
+
+    /**
+     * Tests constructing a complex key by chaining multiple append operations.
+     */
+    public void testAppendComplexKey()
+    {
+        key.append("tables").append("table.").appendIndex(0);
+        key.append("fields.").append("field").appendIndex(1);
+        key.appendAttribute("dataType");
+        assertEquals("Wrong complex key", TESTKEY, key.toString());
+    }
+
+    /**
+     * Tests getting and setting the key's length.
+     */
+    public void testLength()
+    {
+        key.append(TESTPROPS);
+        assertEquals("Wrong length", TESTPROPS.length(), key.length());
+        key.appendAttribute("dataType");
+        assertEquals("Wrong length", TESTKEY.length(), key.length());
+        key.setLength(TESTPROPS.length());
+        assertEquals("Wrong length after shortening", TESTPROPS.length(), key
+                .length());
+        assertEquals("Wrong resulting key", TESTPROPS, key.toString());
+    }
+
+    /**
+     * Tests comparing configuration keys.
+     */
+    public void testEquals()
+    {
+        DefaultConfigurationKey k1 = new DefaultConfigurationKey(
+                expressionEngine, TESTKEY);
+        DefaultConfigurationKey k2 = new DefaultConfigurationKey(
+                expressionEngine, TESTKEY);
+        assertTrue("Keys are not equal", k1.equals(k2));
+        assertTrue("Not reflexiv", k2.equals(k1));
+        assertEquals("Hash codes not equal", k1.hashCode(), k2.hashCode());
+        k2.append("anotherPart");
+        assertFalse("Keys considered equal", k1.equals(k2));
+        assertFalse("Keys considered equal", k2.equals(k1));
+        assertFalse("Key equals null key", k1.equals(null));
+        assertTrue("Faild comparison with string", k1.equals(TESTKEY));
+    }
+
+    /**
+     * Tests determining an attribute key's name.
+     */
+    public void testAttributeName()
+    {
+        assertEquals("Plain key not detected", "test", key
+                .attributeName("test"));
+        assertEquals("Attribute markers not stripped", "dataType", key
+                .attributeName(TESTATTR));
+        assertNull("Null key not processed", key.attributeName(null));
+    }
+
+    /**
+     * Tests to iterate over a simple key.
+     */
+    public void testIterate()
+    {
+        key.append(TESTKEY);
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("No key parts", it.hasNext());
+        assertEquals("Wrong key part", "tables", it.nextKey());
+        assertEquals("Wrong key part", "table", it.nextKey());
+        assertTrue("No index found", it.hasIndex());
+        assertEquals("Wrong index", 0, it.getIndex());
+        assertEquals("Wrong key part", "fields", it.nextKey());
+        assertFalse("Found an index", it.hasIndex());
+        assertEquals("Wrong key part", "field", it.nextKey(true));
+        assertEquals("Wrong index", 1, it.getIndex());
+        assertFalse("Found an attribute", it.isAttribute());
+        assertEquals("Wrong current key", "field", it.currentKey(true));
+        assertEquals("Wrong key part", "dataType", it.nextKey());
+        assertEquals("Wrong decorated key part", "[@dataType]", it
+                .currentKey(true));
+        assertTrue("Attribute not found", it.isAttribute());
+        assertFalse("Too many key parts", it.hasNext());
+        try
+        {
+            it.next();
+            fail("Could iterate over the iteration's end!");
+        }
+        catch (NoSuchElementException nex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests an iteration where the remove() method is called. This is not
+     * supported.
+     */
+    public void testIterateWithRemove()
+    {
+        assertFalse(key.iterator().hasNext());
+        key.append("simple");
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue(it.hasNext());
+        assertEquals("simple", it.next());
+        try
+        {
+            it.remove();
+            fail("Could remove key component!");
+        }
+        catch (UnsupportedOperationException uex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests iterating over some funny keys.
+     */
+    public void testIterateStrangeKeys()
+    {
+        key = new DefaultConfigurationKey(expressionEngine, "key.");
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("Too few key parts", it.hasNext());
+        assertEquals("Wrong key part", "key", it.next());
+        assertFalse("Too many key parts", it.hasNext());
+
+        key = new DefaultConfigurationKey(expressionEngine, ".");
+        it = key.iterator();
+        assertFalse("Simple delimiter key has more parts", it.hasNext());
+
+        key = new DefaultConfigurationKey(expressionEngine,
+                "key().index()undefined(0).test");
+        it = key.iterator();
+        assertEquals("Wrong first part", "key()", it.next());
+        assertFalse("Index detected in first part", it.hasIndex());
+        assertEquals("Wrong second part", "index()undefined", it.nextKey(false));
+        assertTrue("No index detected in second part", it.hasIndex());
+        assertEquals("Wrong index value", 0, it.getIndex());
+    }
+
+    /**
+     * Tests iterating over keys with escaped delimiters.
+     */
+    public void testIterateEscapedDelimiters()
+    {
+        key.append("my..elem");
+        key.append("trailing..dot..");
+        key.append(".strange");
+        assertEquals("my..elem.trailing..dot...strange", key.toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first part", "my.elem", kit.nextKey());
+        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
+        assertEquals("Wrong third part", "strange", kit.nextKey());
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating over keys when a different escaped delimiter is used.
+     */
+    public void testIterateAlternativeEscapeDelimiter()
+    {
+        expressionEngine.setEscapedDelimiter("\\.");
+        key.append("\\.my\\.elem");
+        key.append("trailing\\.dot\\.");
+        key.append(".strange");
+        assertEquals("\\.my\\.elem.trailing\\.dot\\..strange", key.toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first part", ".my.elem", kit.nextKey());
+        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
+        assertEquals("Wrong third part", "strange", kit.nextKey());
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating when no escape delimiter is defined.
+     */
+    public void testIterateWithoutEscapeDelimiter()
+    {
+        expressionEngine.setEscapedDelimiter(null);
+        key.append("..my..elem.trailing..dot...strange");
+        assertEquals("Wrong key", "my..elem.trailing..dot...strange", key
+                .toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        final String[] parts =
+        { "my", "elem", "trailing", "dot", "strange"};
+        for (int i = 0; i < parts.length; i++)
+        {
+            assertEquals("Wrong key part " + i, parts[i], kit.next());
+        }
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating over an attribute key that has an index.
+     */
+    public void testAttributeKeyWithIndex()
+    {
+        key.append(TESTATTR);
+        key.appendIndex(0);
+        assertEquals("Wrong attribute key with index", TESTATTR + "(0)", key
+                .toString());
+
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("No first element", it.hasNext());
+        it.next();
+        assertTrue("Index not found", it.hasIndex());
+        assertEquals("Incorrect index", 0, it.getIndex());
+        assertTrue("Attribute not found", it.isAttribute());
+        assertEquals("Wrong plain key", "dataType", it.currentKey(false));
+        assertEquals("Wrong decorated key", TESTATTR, it.currentKey(true));
+    }
+
+    /**
+     * Tests iteration when the attribute markers equals the property delimiter.
+     */
+    public void testIterateAttributeEqualsPropertyDelimiter()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine.setAttributeStart(expressionEngine
+                .getPropertyDelimiter());
+        key.append("this.isa.key");
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first key part", "this", kit.next());
+        assertFalse("First part is an attribute", kit.isAttribute());
+        assertTrue("First part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong second key part", "isa", kit.next());
+        assertFalse("Second part is an attribute", kit.isAttribute());
+        assertTrue("Second part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong third key part", "key", kit.next());
+        assertTrue("Third part is not an attribute", kit.isAttribute());
+        assertTrue("Third part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong decorated key part", "key", kit.currentKey(true));
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java?rev=365952&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java Wed Jan  4 09:30:35 2006
@@ -0,0 +1,530 @@
+/*
+ * Copyright 2005-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Iterator;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for DefaultExpressionEngine.
+ *
+ * @author Oliver Heger
+ */
+public class TestDefaultExpressionEngine extends TestCase
+{
+    /** Stores the names of the test nodes representing tables. */
+    private static String[] tables =
+    { "users", "documents"};
+
+    /** Stores the types of the test table nodes. */
+    private static String[] tabTypes =
+    { "system", "application"};
+
+    /** Test data fields for the node hierarchy. */
+    private static String[][] fields =
+    {
+    { "uid", "uname", "firstName", "lastName", "email"},
+    { "docid", "name", "creationDate", "authorID", "version"}};
+
+    /** The object to be tested. */
+    DefaultExpressionEngine engine;
+
+    /** The root of a hierarchy with configuration nodes. */
+    ConfigurationNode root;
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        root = setUpNodes();
+        engine = new DefaultExpressionEngine();
+    }
+
+    /**
+     * Tests some simple queries.
+     */
+    public void testQueryKeys()
+    {
+        checkKey("tables.table.name", "name", 2);
+        checkKey("tables.table.fields.field.name", "name", 10);
+        checkKey("tables.table[@type]", "type", 2);
+        checkKey("tables.table(0).fields.field.name", "name", 5);
+        checkKey("tables.table(1).fields.field.name", "name", 5);
+        checkKey("tables.table.fields.field(1).name", "name", 2);
+    }
+
+    /**
+     * Performs some queries and evaluates the values of the result nodes.
+     */
+    public void testQueryNodes()
+    {
+        for (int i = 0; i < tables.length; i++)
+        {
+            checkKeyValue("tables.table(" + i + ").name", "name", tables[i]);
+            checkKeyValue("tables.table(" + i + ")[@type]", "type", tabTypes[i]);
+
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                checkKeyValue("tables.table(" + i + ").fields.field(" + j
+                        + ").name", "name", fields[i][j]);
+            }
+        }
+    }
+
+    /**
+     * Tests querying keys that do not exist.
+     */
+    public void testQueryNonExistingKeys()
+    {
+        checkKey("tables.tablespace.name", null, 0);
+        checkKey("tables.table(2).name", null, 0);
+        checkKey("a complete unknown key", null, 0);
+        checkKey("tables.table(0).fields.field(-1).name", null, 0);
+        checkKey("tables.table(0).fields.field(28).name", null, 0);
+        checkKey("tables.table(0).fields.field().name", null, 0);
+        checkKey("connection.settings.usr.name", null, 0);
+    }
+
+    /**
+     * Tests querying nodes whose names contain a delimiter.
+     */
+    public void testQueryEscapedKeys()
+    {
+        checkKeyValue("connection..settings.usr..name", "usr.name", "scott");
+        checkKeyValue("connection..settings.usr..pwd", "usr.pwd", "tiger");
+    }
+
+    /**
+     * Tests some queries when the same delimiter is used for properties and
+     * attributes.
+     */
+    public void testQueryAttributeEmulation()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+        checkKeyValue("tables.table(0).name", "name", tables[0]);
+        checkKeyValue("tables.table(0).type", "type", tabTypes[0]);
+        checkKey("tables.table.type", "type", 2);
+    }
+
+    /**
+     * Tests accessing the root node.
+     */
+    public void testQueryRootNode()
+    {
+        List nodes = checkKey(null, null, 1);
+        assertSame("Root node not found", root, nodes.get(0));
+        nodes = checkKey("", null, 1);
+        assertSame("Root node not found", root, nodes.get(0));
+        checkKeyValue("[@test]", "test", "true");
+    }
+
+    /**
+     * Tests a different query snytax. Sets other strings for the typical tokens
+     * used by the expression engine.
+     */
+    public void testQueryAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        checkKeyValue("tables/table[1]/name", "name", tables[1]);
+        checkKeyValue("tables/table[0]@type", "type", tabTypes[0]);
+        checkKeyValue("@test", "test", "true");
+        checkKeyValue("connection.settings/usr.name", "usr.name", "scott");
+    }
+
+    /**
+     * Tests obtaining keys for nodes.
+     */
+    public void testNodeKey()
+    {
+        ConfigurationNode node = root.getChild(0);
+        assertEquals("Invalid name for descendant of root", "tables", engine
+                .nodeKey(node, ""));
+        assertEquals("Parent key not respected", "test.tables", engine.nodeKey(
+                node, "test"));
+        assertEquals("Full parent key not taken into account",
+                "a.full.parent.key.tables", engine.nodeKey(node,
+                        "a.full.parent.key"));
+    }
+
+    /**
+     * Tests obtaining keys when the root node is involved.
+     */
+    public void testNodeKeyWithRoot()
+    {
+        assertEquals("Wrong name for root noot", "", engine.nodeKey(root, null));
+        assertEquals("Null name not detected", "test", engine.nodeKey(root,
+                "test"));
+    }
+
+    /**
+     * Tests obtaining keys for attribute nodes.
+     */
+    public void testNodeKeyWithAttribute()
+    {
+        ConfigurationNode node = root.getChild(0).getChild(0).getAttribute(0);
+        assertEquals("Wrong attribute node", "type", node.getName());
+        assertEquals("Wrong attribute key", "tables.table[@type]", engine
+                .nodeKey(node, "tables.table"));
+        assertEquals("Wrong key for root attribute", "[@test]", engine.nodeKey(
+                root.getAttribute(0), ""));
+    }
+
+    /**
+     * Tests obtaining keys for nodes that contain the delimiter character.
+     */
+    public void testNodeKeyWithEscapedDelimiters()
+    {
+        ConfigurationNode node = root.getChild(1);
+        assertEquals("Wrong escaped key", "connection..settings", engine
+                .nodeKey(node, ""));
+        assertEquals("Wrong complex escaped key",
+                "connection..settings.usr..name", engine.nodeKey(node
+                        .getChild(0), engine.nodeKey(node, "")));
+    }
+
+    /**
+     * Tests obtaining node keys when a different syntax is set.
+     */
+    public void testNodeKeyWithAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        assertEquals("Wrong child key", "tables/table", engine.nodeKey(root
+                .getChild(0).getChild(0), "tables"));
+        assertEquals("Wrong attribute key", "@test", engine.nodeKey(root
+                .getAttribute(0), ""));
+
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+        assertEquals("Wrong attribute key", "/test", engine.nodeKey(root
+                .getAttribute(0), ""));
+    }
+
+    /**
+     * Tests adding direct child nodes to the existing hierarchy.
+     */
+    public void testPrepareAddDirectly()
+    {
+        NodeAddData data = engine.prepareAdd(root, "newNode");
+        assertSame("Wrong parent node", root, data.getParent());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong name of new node", "newNode", data.getNewNodeName());
+        assertFalse("New node is an attribute", data.isAttribute());
+
+        data = engine.prepareAdd(root, "tables.table.fields.field.name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong parent node", "field", data.getParent().getName());
+        ConfigurationNode nd = data.getParent().getChild(0);
+        assertEquals("Field has no name node", "name", nd.getName());
+        assertEquals("Incorrect name", "version", nd.getValue());
+    }
+
+    /**
+     * Tests adding when indices are involved.
+     */
+    public void testPrepareAddWithIndex()
+    {
+        NodeAddData data = engine
+                .prepareAdd(root, "tables.table(0).tableSpace");
+        assertEquals("Wrong name of new node", "tableSpace", data
+                .getNewNodeName());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong type of parent node", "table", data.getParent()
+                .getName());
+        ConfigurationNode node = data.getParent().getChild(0);
+        assertEquals("Wrong table", tables[0], node.getValue());
+
+        data = engine.prepareAdd(root, "tables.table(1).fields.field(2).alias");
+        assertEquals("Wrong name of new node", "alias", data.getNewNodeName());
+        assertEquals("Wrong type of parent node", "field", data.getParent()
+                .getName());
+        assertEquals("Wrong field node", "creationDate", data.getParent()
+                .getChild(0).getValue());
+    }
+
+    /**
+     * Tests adding new attributes.
+     */
+    public void testPrepareAddAttribute()
+    {
+        NodeAddData data = engine.prepareAdd(root,
+                "tables.table(0)[@tableSpace]");
+        assertEquals("Wrong table node", tables[0], data.getParent()
+                .getChild(0).getValue());
+        assertEquals("Wrong name of new node", "tableSpace", data
+                .getNewNodeName());
+        assertTrue("Attribute not detected", data.isAttribute());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+
+        data = engine.prepareAdd(root, "[@newAttr]");
+        assertSame("Root node is not parent", root, data.getParent());
+        assertEquals("Wrong name of new node", "newAttr", data.getNewNodeName());
+        assertTrue("Attribute not detected", data.isAttribute());
+    }
+
+    /**
+     * Tests add operations where complete pathes are added.
+     */
+    public void testPrepareAddWithPath()
+    {
+        NodeAddData data = engine.prepareAdd(root,
+                "tables.table(1).fields.field(-1).name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "field"});
+        assertEquals("Wrong type of parent node", "fields", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "tables.table(-1).name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "table"});
+        assertEquals("Wrong type of parent node", "tables", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "a.complete.new.path");
+        assertEquals("Wrong name of new node", "path", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new"});
+        assertSame("Root is not parent", root, data.getParent());
+    }
+
+    /**
+     * Tests add operations when property and attribute delimiters are equal.
+     * Then it is not possible to add new attribute nodes.
+     */
+    public void testPrepareAddWithSameAttributeDelimiter()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+
+        NodeAddData data = engine.prepareAdd(root, "tables.table(0).test");
+        assertEquals("Wrong name of new node", "test", data.getNewNodeName());
+        assertFalse("New node is an attribute", data.isAttribute());
+        assertEquals("Wrong type of parent node", "table", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "a.complete.new.path");
+        assertFalse("New node is an attribute", data.isAttribute());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new"});
+    }
+
+    /**
+     * Tests add operations when an alternative syntax is set.
+     */
+    public void testPrepareAddWithAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        NodeAddData data = engine.prepareAdd(root, "tables/table[0]/test");
+        assertEquals("Wrong name of new node", "test", data.getNewNodeName());
+        assertFalse("New node is attribute", data.isAttribute());
+        assertEquals("Wrong parent node", tables[0], data.getParent().getChild(
+                0).getValue());
+
+        data = engine.prepareAdd(root, "a/complete/new/path@attr");
+        assertEquals("Wrong name of new attribute", "attr", data
+                .getNewNodeName());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new", "path"});
+        assertSame("Root is not parent", root, data.getParent());
+    }
+
+    /**
+     * Tests using invalid keys, e.g. if something should be added to
+     * attributes.
+     */
+    public void testPrepareAddInvalidKeys()
+    {
+        try
+        {
+            engine.prepareAdd(root, "tables.table(0)[@type].new");
+            fail("Could add node to existing attribute!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+
+        try
+        {
+            engine
+                    .prepareAdd(root,
+                            "a.complete.new.path.with.an[@attribute].at.a.non.allowed[@position]");
+            fail("Could add invalid path!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+
+        try
+        {
+            engine.prepareAdd(root, null);
+            fail("Could add null key!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+
+        try
+        {
+            engine.prepareAdd(root, "");
+            fail("Could add undefined key!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Creates a node hierarchy for testing that consists of tables, their
+     * fields, and some additional data:
+     *
+     * <pre>
+     *  tables
+     *       table
+     *          name
+     *          fields
+     *              field
+     *                  name
+     *              field
+     *                  name
+     * </pre>
+     *
+     * @return the root of the test node hierarchy
+     */
+    protected ConfigurationNode setUpNodes()
+    {
+        DefaultConfigurationNode rootNode = new DefaultConfigurationNode();
+
+        DefaultConfigurationNode nodeTables = new DefaultConfigurationNode(
+                "tables");
+        rootNode.addChild(nodeTables);
+        for (int i = 0; i < tables.length; i++)
+        {
+            DefaultConfigurationNode nodeTable = new DefaultConfigurationNode(
+                    "table");
+            nodeTables.addChild(nodeTable);
+            nodeTable.addChild(new DefaultConfigurationNode("name", tables[i]));
+            nodeTable.addAttribute(new DefaultConfigurationNode("type",
+                    tabTypes[i]));
+            DefaultConfigurationNode nodeFields = new DefaultConfigurationNode(
+                    "fields");
+            nodeTable.addChild(nodeFields);
+
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                nodeFields.addChild(createFieldNode(fields[i][j]));
+            }
+        }
+
+        DefaultConfigurationNode nodeConn = new DefaultConfigurationNode(
+                "connection.settings");
+        rootNode.addChild(nodeConn);
+        nodeConn.addChild(new DefaultConfigurationNode("usr.name", "scott"));
+        nodeConn.addChild(new DefaultConfigurationNode("usr.pwd", "tiger"));
+        rootNode.addAttribute(new DefaultConfigurationNode("test", "true"));
+
+        return rootNode;
+    }
+
+    /**
+     * Configures the expression engine to use a different syntax.
+     */
+    private void setUpAlternativeSyntax()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart("@");
+        engine.setPropertyDelimiter("/");
+        engine.setEscapedDelimiter(null);
+        engine.setIndexStart("[");
+        engine.setIndexEnd("]");
+    }
+
+    /**
+     * Helper method for checking the evaluation of a key. Queries the
+     * expression engine and tests if the expected results are returned.
+     *
+     * @param key the key
+     * @param name the name of the nodes to be returned
+     * @param count the number of expected result nodes
+     * @return the list with the results of the query
+     */
+    private List checkKey(String key, String name, int count)
+    {
+        List nodes = engine.query(root, key);
+        assertEquals("Wrong number of result nodes for key " + key, count,
+                nodes.size());
+        for (Iterator it = nodes.iterator(); it.hasNext();)
+        {
+            assertEquals("Wrong result node for key " + key, name,
+                    ((ConfigurationNode) it.next()).getName());
+        }
+        return nodes;
+    }
+
+    /**
+     * Helper method for checking the value of a node specified by the given
+     * key. This method evaluates the key and checks whether the resulting node
+     * has the expected value.
+     *
+     * @param key the key
+     * @param name the expected name of the result node
+     * @param value the expected value of the result node
+     */
+    private void checkKeyValue(String key, String name, String value)
+    {
+        List nodes = checkKey(key, name, 1);
+        assertEquals("Wrong value for key " + key, value,
+                ((ConfigurationNode) nodes.get(0)).getValue());
+    }
+
+    /**
+     * Helper method for checking the path of an add operation.
+     *
+     * @param data the add data object
+     * @param expected the expected path nodes
+     */
+    private void checkNodePath(NodeAddData data, String[] expected)
+    {
+        assertEquals("Wrong number of path nodes", expected.length, data
+                .getPathNodes().size());
+        Iterator it = data.getPathNodes().iterator();
+        for (int i = 0; i < expected.length; i++)
+        {
+            assertEquals("Wrong path node " + i, expected[i], it.next());
+        }
+    }
+
+    /**
+     * Helper method for creating a field node with its children for the test
+     * node hierarchy.
+     *
+     * @param name the name of the field
+     * @return the field node
+     */
+    private static ConfigurationNode createFieldNode(String name)
+    {
+        DefaultConfigurationNode nodeField = new DefaultConfigurationNode(
+                "field");
+        nodeField.addChild(new DefaultConfigurationNode("name", name));
+        return nodeField;
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message