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