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