    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 */
    017package org.apache.commons.configuration2.tree;
    018
    019import java.util.Iterator;
    020import java.util.NoSuchElementException;
    021
    022import org.apache.commons.lang3.StringUtils;
    023
    024/**
    025 * <p>
    026 * A simple class that supports creation of and iteration on configuration keys
    027 * supported by a {@link DefaultExpressionEngine} object.
    028 * </p>
    029 * <p>
    030 * For key creation the class works similar to a StringBuffer: There are several
    031 * {@code appendXXXX()} methods with which single parts of a key can be
    032 * constructed. All these methods return a reference to the actual object so
    033 * they can be written in a chain. When using this methods the exact syntax for
    034 * keys need not be known.
    035 * </p>
    036 * <p>
    037 * This class also defines a specialized iterator for configuration keys. With
    038 * such an iterator a key can be tokenized into its single parts. For each part
    039 * it can be checked whether it has an associated index.
    040 * </p>
    041 * <p>
    042 * Instances of this class are always associated with an instance of
    043 * {@link DefaultExpressionEngine}, from which the current
    044 * delimiters are obtained. So key creation and parsing is specific to this
    045 * associated expression engine.
    046 * </p>
    047 *
    048 * @since 1.3
    049 */
    050public class DefaultConfigurationKey
    051{
    052    /** Constant for the initial StringBuffer size. */
    053    private static final int INITIAL_SIZE = 32;
    054
    055    /** Stores a reference to the associated expression engine. */
    056    private final DefaultExpressionEngine expressionEngine;
    057
    058    /** Holds a buffer with the so far created key. */
    059    private final StringBuilder keyBuffer;
    060
    061    /**
    062     * Creates a new instance of {@code DefaultConfigurationKey} and sets
    063     * the associated expression engine.
    064     *
    065     * @param engine the expression engine (must not be <b>null</b>)
    066     * @throws IllegalArgumentException if the expression engine is <b>null</b>
    067     */
    068    public DefaultConfigurationKey(final DefaultExpressionEngine engine)
    069    {
    070        this(engine, null);
    071    }
    072
    073    /**
    074     * Creates a new instance of {@code DefaultConfigurationKey} and sets the
    075     * associated expression engine and an initial key.
    076     *
    077     * @param engine the expression engine (must not be <b>null</b>)
    078     * @param key the key to be wrapped
    079     * @throws IllegalArgumentException if the expression engine is <b>null</b>
    080     */
    081    public DefaultConfigurationKey(final DefaultExpressionEngine engine, final String key)
    082    {
    083        if (engine == null)
    084        {
    085            throw new IllegalArgumentException(
    086                    "Expression engine must not be null!");
    087        }
    088        expressionEngine = engine;
    089        if (key != null)
    090        {
    091            keyBuffer = new StringBuilder(trim(key));
    092        }
    093        else
    094        {
    095            keyBuffer = new StringBuilder(INITIAL_SIZE);
    096        }
    097    }
    098
    099    /**
    100     * Returns the associated default expression engine.
    101     *
    102     * @return the associated expression engine
    103     */
    104    public DefaultExpressionEngine getExpressionEngine()
    105    {
    106        return expressionEngine;
    107    }
    108
    109    /**
    110     * Appends the name of a property to this key. If necessary, a property
    111     * delimiter will be added. If the boolean argument is set to <b>true</b>,
    112     * property delimiters contained in the property name will be escaped.
    113     *
    114     * @param property the name of the property to be added
    115     * @param escape a flag if property delimiters in the passed in property name
    116     * should be escaped
    117     * @return a reference to this object
    118     */
    119    public DefaultConfigurationKey append(final String property, final boolean escape)
    120    {
    121        String key;
    122        if (escape && property != null)
    123        {
    124            key = escapeDelimiters(property);
    125        }
    126        else
    127        {
    128            key = property;
    129        }
    130        key = trim(key);
    131
    132        if (keyBuffer.length() > 0 && !isAttributeKey(property)
    133                && key.length() > 0)
    134        {
    135            keyBuffer.append(getSymbols().getPropertyDelimiter());
    136        }
    137
    138        keyBuffer.append(key);
    139        return this;
    140    }
    141
    142    /**
    143     * Appends the name of a property to this key. If necessary, a property
    144     * delimiter will be added. Property delimiters in the given string will not
    145     * be escaped.
    146     *
    147     * @param property the name of the property to be added
    148     * @return a reference to this object
    149     */
    150    public DefaultConfigurationKey append(final String property)
    151    {
    152        return append(property, false);
    153    }
    154
    155    /**
    156     * Appends an index to this configuration key.
    157     *
    158     * @param index the index to be appended
    159     * @return a reference to this object
    160     */
    161    public DefaultConfigurationKey appendIndex(final int index)
    162    {
    163        keyBuffer.append(getSymbols().getIndexStart());
    164        keyBuffer.append(index);
    165        keyBuffer.append(getSymbols().getIndexEnd());
    166        return this;
    167    }
    168
    169    /**
    170     * Appends an attribute to this configuration key.
    171     *
    172     * @param attr the name of the attribute to be appended
    173     * @return a reference to this object
    174     */
    175    public DefaultConfigurationKey appendAttribute(final String attr)
    176    {
    177        keyBuffer.append(constructAttributeKey(attr));
    178        return this;
    179    }
    180
    181    /**
    182     * Returns the actual length of this configuration key.
    183     *
    184     * @return the length of this key
    185     */
    186    public int length()
    187    {
    188        return keyBuffer.length();
    189    }
    190
    191    /**
    192     * Sets the new length of this configuration key. With this method it is
    193     * possible to truncate the key, e.g. to return to a state prior calling
    194     * some {@code append()} methods. The semantic is the same as the
    195     * {@code setLength()} method of {@code StringBuilder}.
    196     *
    197     * @param len the new length of the key
    198     */
    199    public void setLength(final int len)
    200    {
    201        keyBuffer.setLength(len);
    202    }
    203    /**
    204     * Returns a configuration key object that is initialized with the part
    205     * of the key that is common to this key and the passed in key.
    206     *
    207     * @param other the other key
    208     * @return a key object with the common key part
    209     */
    210    public DefaultConfigurationKey commonKey(final DefaultConfigurationKey other)
    211    {
    212        if (other == null)
    213        {
    214            throw new IllegalArgumentException("Other key must no be null!");
    215        }
    216
    217        final DefaultConfigurationKey result = new DefaultConfigurationKey(getExpressionEngine());
    218        final KeyIterator it1 = iterator();
    219        final KeyIterator it2 = other.iterator();
    220
    221        while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
    222        {
    223            if (it1.isAttribute())
    224            {
    225                result.appendAttribute(it1.currentKey());
    226            }
    227            else
    228            {
    229                result.append(it1.currentKey());
    230                if (it1.hasIndex)
    231                {
    232                    result.appendIndex(it1.getIndex());
    233                }
    234            }
    235        }
    236
    237        return result;
    238    }
    239
    240    /**
    241     * Returns the &quot;difference key&quot; to a given key. This value
    242     * is the part of the passed in key that differs from this key. There is
    243     * the following relation:
    244     * {@code other = key.commonKey(other) + key.differenceKey(other)}
    245     * for an arbitrary configuration key {@code key}.
    246     *
    247     * @param other the key for which the difference is to be calculated
    248     * @return the difference key
    249     */
    250    public DefaultConfigurationKey differenceKey(final DefaultConfigurationKey other)
    251    {
    252        final DefaultConfigurationKey common = commonKey(other);
    253        final DefaultConfigurationKey result = new DefaultConfigurationKey(getExpressionEngine());
    254
    255        if (common.length() < other.length())
    256        {
    257            final String k = other.toString().substring(common.length());
    258            // skip trailing delimiters
    259            int i = 0;
    260            while (i < k.length()
    261                    && String.valueOf(k.charAt(i)).equals(
    262                            getSymbols().getPropertyDelimiter()))
    263            {
    264                i++;
    265            }
    266
    267            if (i < k.length())
    268            {
    269                result.append(k.substring(i));
    270            }
    271        }
    272
    273        return result;
    274    }
    275
    276    /**
    277     * Checks if two {@code ConfigurationKey} objects are equal. Two instances
    278     * of this class are considered equal if they have the same content (i.e.
    279     * their internal string representation is equal). The expression engine
    280     * property is not taken into account.
    281     *
    282     * @param obj the object to compare
    283     * @return a flag if both objects are equal
    284     */
    285    @Override
    286    public boolean equals(final Object obj)
    287    {
    288        if (this == obj)
    289        {
    290            return true;
    291        }
    292        if (!(obj instanceof DefaultConfigurationKey))
    293        {
    294            return false;
    295        }
    296
    297        final DefaultConfigurationKey c = (DefaultConfigurationKey) obj;
    298        return keyBuffer.toString().equals(c.toString());
    299    }
    300
    301    /**
    302     * Returns the hash code for this object.
    303     *
    304     * @return the hash code
    305     */
    306    @Override
    307    public int hashCode()
    308    {
    309        return String.valueOf(keyBuffer).hashCode();
    310    }
    311
    312    /**
    313     * Returns a string representation of this object. This is the configuration
    314     * key as a plain string.
    315     *
    316     * @return a string for this object
    317     */
    318    @Override
    319    public String toString()
    320    {
    321        return keyBuffer.toString();
    322    }
    323
    324    /**
    325     * Tests if the specified key represents an attribute according to the
    326     * current expression engine.
    327     *
    328     * @param key the key to be checked
    329     * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
    330     */
    331    public boolean isAttributeKey(final String key)
    332    {
    333        if (key == null)
    334        {
    335            return false;
    336        }
    337
    338        return key.startsWith(getSymbols().getAttributeStart())
    339                && (getSymbols().getAttributeEnd() == null || key
    340                        .endsWith(getSymbols().getAttributeEnd()));
    341    }
    342
    343    /**
    344     * Decorates the given key so that it represents an attribute. Adds special
    345     * start and end markers. The passed in string will be modified only if does
    346     * not already represent an attribute.
    347     *
    348     * @param key the key to be decorated
    349     * @return the decorated attribute key
    350     */
    351    public String constructAttributeKey(final String key)
    352    {
    353        if (key == null)
    354        {
    355            return StringUtils.EMPTY;
    356        }
    357        if (isAttributeKey(key))
    358        {
    359            return key;
    360        }
    361        final StringBuilder buf = new StringBuilder();
    362        buf.append(getSymbols().getAttributeStart()).append(key);
    363        if (getSymbols().getAttributeEnd() != null)
    364        {
    365            buf.append(getSymbols().getAttributeEnd());
    366        }
    367        return buf.toString();
    368    }
    369
    370    /**
    371     * Extracts the name of the attribute from the given attribute key. This
    372     * method removes the attribute markers - if any - from the specified key.
    373     *
    374     * @param key the attribute key
    375     * @return the name of the corresponding attribute
    376     */
    377    public String attributeName(final String key)
    378    {
    379        return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
    380    }
    381
    382    /**
    383     * Removes leading property delimiters from the specified key.
    384     *
    385     * @param key the key
    386     * @return the key with removed leading property delimiters
    387     */
    388    public String trimLeft(final String key)
    389    {
    390        if (key == null)
    391        {
    392            return StringUtils.EMPTY;
    393        }
    394        String result = key;
    395        while (hasLeadingDelimiter(result))
    396        {
    397            result = result.substring(getSymbols()
    398                    .getPropertyDelimiter().length());
    399        }
    400        return result;
    401    }
    402
    403    /**
    404     * Removes trailing property delimiters from the specified key.
    405     *
    406     * @param key the key
    407     * @return the key with removed trailing property delimiters
    408     */
    409    public String trimRight(final String key)
    410    {
    411        if (key == null)
    412        {
    413            return StringUtils.EMPTY;
    414        }
    415        String result = key;
    416        while (hasTrailingDelimiter(result))
    417        {
    418            result = result
    419                    .substring(0, result.length()
    420                            - getSymbols().getPropertyDelimiter()
    421                                    .length());
    422        }
    423        return result;
    424    }
    425
    426    /**
    427     * Removes delimiters at the beginning and the end of the specified key.
    428     *
    429     * @param key the key
    430     * @return the key with removed property delimiters
    431     */
    432    public String trim(final String key)
    433    {
    434        return trimRight(trimLeft(key));
    435    }
    436
    437    /**
    438     * Returns an iterator for iterating over the single components of this
    439     * configuration key.
    440     *
    441     * @return an iterator for this key
    442     */
    443    public KeyIterator iterator()
    444    {
    445        return new KeyIterator();
    446    }
    447
    448    /**
    449     * Helper method that checks if the specified key ends with a property
    450     * delimiter.
    451     *
    452     * @param key the key to check
    453     * @return a flag if there is a trailing delimiter
    454     */
    455    private boolean hasTrailingDelimiter(final String key)
    456    {
    457        return key.endsWith(getSymbols().getPropertyDelimiter())
    458                && (getSymbols().getEscapedDelimiter() == null || !key
    459                        .endsWith(getSymbols().getEscapedDelimiter()));
    460    }
    461
    462    /**
    463     * Helper method that checks if the specified key starts with a property
    464     * delimiter.
    465     *
    466     * @param key the key to check
    467     * @return a flag if there is a leading delimiter
    468     */
    469    private boolean hasLeadingDelimiter(final String key)
    470    {
    471        return key.startsWith(getSymbols().getPropertyDelimiter())
    472                && (getSymbols().getEscapedDelimiter() == null || !key
    473                        .startsWith(getSymbols().getEscapedDelimiter()));
    474    }
    475
    476    /**
    477     * Helper method for removing attribute markers from a key.
    478     *
    479     * @param key the key
    480     * @return the key with removed attribute markers
    481     */
    482    private String removeAttributeMarkers(final String key)
    483    {
    484        return key
    485                .substring(
    486                        getSymbols().getAttributeStart().length(),
    487                        key.length()
    488                                - ((getSymbols().getAttributeEnd() != null) ? getSymbols()
    489                                        .getAttributeEnd().length()
    490                                        : 0));
    491    }
    492
    493    /**
    494     * Unescapes the delimiters in the specified string.
    495     *
    496     * @param key the key to be unescaped
    497     * @return the unescaped key
    498     */
    499    private String unescapeDelimiters(final String key)
    500    {
    501        return getSymbols().getEscapedDelimiter() == null ? key
    502                : StringUtils.replace(key, getSymbols()
    503                        .getEscapedDelimiter(), getSymbols()
    504                        .getPropertyDelimiter());
    505    }
    506
    507    /**
    508     * Returns the symbols object from the associated expression engine.
    509     *
    510     * @return the {@code DefaultExpressionEngineSymbols}
    511     */
    512    private DefaultExpressionEngineSymbols getSymbols()
    513    {
    514        return getExpressionEngine().getSymbols();
    515    }
    516
    517    /**
    518     * Escapes the delimiters in the specified string.
    519     *
    520     * @param key the key to be escaped
    521     * @return the escaped key
    522     */
    523    private String escapeDelimiters(final String key)
    524    {
    525        return (getSymbols().getEscapedDelimiter() == null || key
    526                .indexOf(getSymbols().getPropertyDelimiter()) < 0) ? key
    527                : StringUtils.replace(key, getSymbols()
    528                        .getPropertyDelimiter(), getSymbols()
    529                        .getEscapedDelimiter());
    530    }
    531
    532    /**
    533     * Helper method for comparing two key parts.
    534     *
    535     * @param it1 the iterator with the first part
    536     * @param it2 the iterator with the second part
    537     * @return a flag if both parts are equal
    538     */
    539    private static boolean partsEqual(final KeyIterator it1, final KeyIterator it2)
    540    {
    541        return it1.nextKey().equals(it2.nextKey())
    542                && it1.getIndex() == it2.getIndex()
    543                && it1.isAttribute() == it2.isAttribute();
    544    }
    545
    546    /**
    547     * A specialized iterator class for tokenizing a configuration key. This
    548     * class implements the normal iterator interface. In addition it provides
    549     * some specific methods for configuration keys.
    550     */
    551    public class KeyIterator implements Iterator<Object>, Cloneable
    552    {
    553        /** Stores the current key name. */
    554        private String current;
    555
    556        /** Stores the start index of the actual token. */
    557        private int startIndex;
    558
    559        /** Stores the end index of the actual token. */
    560        private int endIndex;
    561
    562        /** Stores the index of the actual property if there is one. */
    563        private int indexValue;
    564
    565        /** Stores a flag if the actual property has an index. */
    566        private boolean hasIndex;
    567
    568        /** Stores a flag if the actual property is an attribute. */
    569        private boolean attribute;
    570
    571        /**
    572         * Returns the next key part of this configuration key. This is a short
    573         * form of {@code nextKey(false)}.
    574         *
    575         * @return the next key part
    576         */
    577        public String nextKey()
    578        {
    579            return nextKey(false);
    580        }
    581
    582        /**
    583         * Returns the next key part of this configuration key. The boolean
    584         * parameter indicates wheter a decorated key should be returned. This
    585         * affects only attribute keys: if the parameter is <b>false</b>, the
    586         * attribute markers are stripped from the key; if it is <b>true</b>,
    587         * they remain.
    588         *
    589         * @param decorated a flag if the decorated key is to be returned
    590         * @return the next key part
    591         */
    592        public String nextKey(final boolean decorated)
    593        {
    594            if (!hasNext())
    595            {
    596                throw new NoSuchElementException("No more key parts!");
    597            }
    598
    599            hasIndex = false;
    600            indexValue = -1;
    601            final String key = findNextIndices();
    602
    603            current = key;
    604            hasIndex = checkIndex(key);
    605            attribute = checkAttribute(current);
    606
    607            return currentKey(decorated);
    608        }
    609
    610        /**
    611         * Checks if there is a next element.
    612         *
    613         * @return a flag if there is a next element
    614         */
    615        @Override
    616        public boolean hasNext()
    617        {
    618            return endIndex < keyBuffer.length();
    619        }
    620
    621        /**
    622         * Returns the next object in the iteration.
    623         *
    624         * @return the next object
    625         */
    626        @Override
    627        public Object next()
    628        {
    629            return nextKey();
    630        }
    631
    632        /**
    633         * Removes the current object in the iteration. This method is not
    634         * supported by this iterator type, so an exception is thrown.
    635         */
    636        @Override
    637        public void remove()
    638        {
    639            throw new UnsupportedOperationException("Remove not supported!");
    640        }
    641
    642        /**
    643         * Returns the current key of the iteration (without skipping to the
    644         * next element). This is the same key the previous {@code next()}
    645         * call had returned. (Short form of {@code currentKey(false)}.
    646         *
    647         * @return the current key
    648         */
    649        public String currentKey()
    650        {
    651            return currentKey(false);
    652        }
    653
    654        /**
    655         * Returns the current key of the iteration (without skipping to the
    656         * next element). The boolean parameter indicates wheter a decorated key
    657         * should be returned. This affects only attribute keys: if the
    658         * parameter is <b>false</b>, the attribute markers are stripped from
    659         * the key; if it is <b>true</b>, they remain.
    660         *
    661         * @param decorated a flag if the decorated key is to be returned
    662         * @return the current key
    663         */
    664        public String currentKey(final boolean decorated)
    665        {
    666            return decorated && !isPropertyKey() ? constructAttributeKey(current)
    667                    : current;
    668        }
    669
    670        /**
    671         * Returns a flag if the current key is an attribute. This method can be
    672         * called after {@code next()}.
    673         *
    674         * @return a flag if the current key is an attribute
    675         */
    676        public boolean isAttribute()
    677        {
    678            // if attribute emulation mode is active, the last part of a key is
    679            // always an attribute key, too
    680            return attribute || (isAttributeEmulatingMode() && !hasNext());
    681        }
    682
    683        /**
    684         * Returns a flag whether the current key refers to a property (i.e. is
    685         * no special attribute key). Usually this method will return the
    686         * opposite of {@code isAttribute()}, but if the delimiters for
    687         * normal properties and attributes are set to the same string, it is
    688         * possible that both methods return <b>true</b>.
    689         *
    690         * @return a flag if the current key is a property key
    691         * @see #isAttribute()
    692         */
    693        public boolean isPropertyKey()
    694        {
    695            return !attribute;
    696        }
    697
    698        /**
    699         * Returns the index value of the current key. If the current key does
    700         * not have an index, return value is -1. This method can be called
    701         * after {@code next()}.
    702         *
    703         * @return the index value of the current key
    704         */
    705        public int getIndex()
    706        {
    707            return indexValue;
    708        }
    709
    710        /**
    711         * Returns a flag if the current key has an associated index. This
    712         * method can be called after {@code next()}.
    713         *
    714         * @return a flag if the current key has an index
    715         */
    716        public boolean hasIndex()
    717        {
    718            return hasIndex;
    719        }
    720
    721        /**
    722         * Creates a clone of this object.
    723         *
    724         * @return a clone of this object
    725         */
    726        @Override
    727        public Object clone()
    728        {
    729            try
    730            {
    731                return super.clone();
    732            }
    733            catch (final CloneNotSupportedException cex)
    734            {
    735                // should not happen
    736                return null;
    737            }
    738        }
    739
    740        /**
    741         * Helper method for determining the next indices.
    742         *
    743         * @return the next key part
    744         */
    745        private String findNextIndices()
    746        {
    747            startIndex = endIndex;
    748            // skip empty names
    749            while (startIndex < length()
    750                    && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
    751            {
    752                startIndex += getSymbols().getPropertyDelimiter()
    753                        .length();
    754            }
    755
    756            // Key ends with a delimiter?
    757            if (startIndex >= length())
    758            {
    759                endIndex = length();
    760                startIndex = endIndex - 1;
    761                return keyBuffer.substring(startIndex, endIndex);
    762            }
    763            return nextKeyPart();
    764        }
    765
    766        /**
    767         * Helper method for extracting the next key part. Takes escaping of
    768         * delimiter characters into account.
    769         *
    770         * @return the next key part
    771         */
    772        private String nextKeyPart()
    773        {
    774            int attrIdx = keyBuffer.toString().indexOf(
    775                    getSymbols().getAttributeStart(), startIndex);
    776            if (attrIdx < 0 || attrIdx == startIndex)
    777            {
    778                attrIdx = length();
    779            }
    780
    781            int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
    782                    attrIdx);
    783            if (delIdx < 0)
    784            {
    785                delIdx = attrIdx;
    786            }
    787
    788            endIndex = Math.min(attrIdx, delIdx);
    789            return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
    790        }
    791
    792        /**
    793         * Searches the next unescaped delimiter from the given position.
    794         *
    795         * @param key the key
    796         * @param pos the start position
    797         * @param endPos the end position
    798         * @return the position of the next delimiter or -1 if there is none
    799         */
    800        private int nextDelimiterPos(final String key, final int pos, final int endPos)
    801        {
    802            int delimiterPos = pos;
    803            boolean found = false;
    804
    805            do
    806            {
    807                delimiterPos = key.indexOf(getSymbols()
    808                        .getPropertyDelimiter(), delimiterPos);
    809                if (delimiterPos < 0 || delimiterPos >= endPos)
    810                {
    811                    return -1;
    812                }
    813                final int escapePos = escapedPosition(key, delimiterPos);
    814                if (escapePos < 0)
    815                {
    816                    found = true;
    817                }
    818                else
    819                {
    820                    delimiterPos = escapePos;
    821                }
    822            }
    823            while (!found);
    824
    825            return delimiterPos;
    826        }
    827
    828        /**
    829         * Checks if a delimiter at the specified position is escaped. If this
    830         * is the case, the next valid search position will be returned.
    831         * Otherwise the return value is -1.
    832         *
    833         * @param key the key to check
    834         * @param pos the position where a delimiter was found
    835         * @return information about escaped delimiters
    836         */
    837        private int escapedPosition(final String key, final int pos)
    838        {
    839            if (getSymbols().getEscapedDelimiter() == null)
    840            {
    841                // nothing to escape
    842                return -1;
    843            }
    844            final int escapeOffset = escapeOffset();
    845            if (escapeOffset < 0 || escapeOffset > pos)
    846            {
    847                // No escaping possible at this position
    848                return -1;
    849            }
    850
    851            final int escapePos = key.indexOf(getSymbols()
    852                    .getEscapedDelimiter(), pos - escapeOffset);
    853            if (escapePos <= pos && escapePos >= 0)
    854            {
    855                // The found delimiter is escaped. Next valid search position
    856                // is behind the escaped delimiter.
    857                return escapePos
    858                        + getSymbols().getEscapedDelimiter().length();
    859            }
    860            return -1;
    861        }
    862
    863        /**
    864         * Determines the relative offset of an escaped delimiter in relation to
    865         * a delimiter. Depending on the used delimiter and escaped delimiter
    866         * tokens the position where to search for an escaped delimiter is
    867         * different. If, for instance, the dot character (&quot;.&quot;) is
    868         * used as delimiter, and a doubled dot (&quot;..&quot;) as escaped
    869         * delimiter, the escaped delimiter starts at the same position as the
    870         * delimiter. If the token &quot;\.&quot; was used, it would start one
    871         * character before the delimiter because the delimiter character
    872         * &quot;.&quot; is the second character in the escaped delimiter
    873         * string. This relation will be determined by this method. For this to
    874         * work the delimiter string must be contained in the escaped delimiter
    875         * string.
    876         *
    877         * @return the relative offset of the escaped delimiter in relation to a
    878         * delimiter
    879         */
    880        private int escapeOffset()
    881        {
    882            return getSymbols().getEscapedDelimiter().indexOf(
    883                    getSymbols().getPropertyDelimiter());
    884        }
    885
    886        /**
    887         * Helper method for checking if the passed key is an attribute. If this
    888         * is the case, the internal fields will be set.
    889         *
    890         * @param key the key to be checked
    891         * @return a flag if the key is an attribute
    892         */
    893        private boolean checkAttribute(final String key)
    894        {
    895            if (isAttributeKey(key))
    896            {
    897                current = removeAttributeMarkers(key);
    898                return true;
    899            }
    900            return false;
    901        }
    902
    903        /**
    904         * Helper method for checking if the passed key contains an index. If
    905         * this is the case, internal fields will be set.
    906         *
    907         * @param key the key to be checked
    908         * @return a flag if an index is defined
    909         */
    910        private boolean checkIndex(final String key)
    911        {
    912            boolean result = false;
    913
    914            try
    915            {
    916                final int idx = key.lastIndexOf(getSymbols().getIndexStart());
    917                if (idx > 0)
    918                {
    919                    final int endidx = key.indexOf(getSymbols().getIndexEnd(),
    920                            idx);
    921
    922                    if (endidx > idx + 1)
    923                    {
    924                        indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
    925                        current = key.substring(0, idx);
    926                        result = true;
    927                    }
    928                }
    929            }
    930            catch (final NumberFormatException nfe)
    931            {
    932                result = false;
    933            }
    934
    935            return result;
    936        }
    937
    938        /**
    939         * Returns a flag whether attributes are marked the same way as normal
    940         * property keys. We call this the &quot;attribute emulating mode&quot;.
    941         * When navigating through node hierarchies it might be convenient to
    942         * treat attributes the same way than other child nodes, so an
    943         * expression engine supports to set the attribute markers to the same
    944         * value than the property delimiter. If this is the case, some special
    945         * checks have to be performed.
    946         *
    947         * @return a flag if attributes and normal property keys are treated the
    948         * same way
    949         */
    950        private boolean isAttributeEmulatingMode()
    951        {
    952            return getSymbols().getAttributeEnd() == null
    953                    && StringUtils.equals(getSymbols()
    954                            .getPropertyDelimiter(), getSymbols()
    955                            .getAttributeStart());
    956        }
    957    }
    958}