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}