View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.configuration.tree;
    18
    19  import java.util.Iterator;
    20  import java.util.NoSuchElementException;
    21
    22  import org.apache.commons.lang.StringUtils;
    23
    24  /***
    25   * <p>
    26   * A simple class that supports creation of and iteration on configuration keys
    27   * supported by a <code>{@link DefaultExpressionEngine}</code> object.
    28   * </p>
    29   * <p>
    30   * For key creation the class works similar to a StringBuffer: There are several
    31   * <code>appendXXXX()</code> methods with which single parts of a key can be
    32   * constructed. All these methods return a reference to the actual object so
    33   * they can be written in a chain. When using this methods the exact syntax for
    34   * keys need not be known.
    35   * </p>
    36   * <p>
    37   * This class also defines a specialized iterator for configuration keys. With
    38   * such an iterator a key can be tokenized into its single parts. For each part
    39   * it can be checked whether it has an associated index.
    40   * </p>
    41   * <p>
    42   * Instances of this class are always associated with an instance of
    43   * <code>{@link DefaultExpressionEngine}</code>, from which the current
    44   * delimiters are obtained. So key creation and parsing is specific to this
    45   * associated expression engine.
    46   * </p>
    47   *
    48   * @since 1.3
    49   * @author Oliver Heger
    50   * @version $Id: DefaultConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $
    51   */
    52  public class DefaultConfigurationKey
    53  {
    54      /*** Constant for the initial StringBuffer size. */
    55      private static final int INITIAL_SIZE = 32;
    56
    57      /*** Stores a reference to the associated expression engine. */
    58      private DefaultExpressionEngine expressionEngine;
    59
    60      /*** Holds a buffer with the so far created key. */
    61      private StringBuffer keyBuffer;
    62
    63      /***
    64       * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
    65       * the associated expression engine.
    66       *
    67       * @param engine the expression engine
    68       */
    69      public DefaultConfigurationKey(DefaultExpressionEngine engine)
    70      {
    71          keyBuffer = new StringBuffer(INITIAL_SIZE);
    72          setExpressionEngine(engine);
    73      }
    74
    75      /***
    76       * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
    77       * the associated expression engine and an initial key.
    78       *
    79       * @param engine the expression engine
    80       * @param key the key to be wrapped
    81       */
    82      public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
    83      {
    84          setExpressionEngine(engine);
    85          keyBuffer = new StringBuffer(trim(key));
    86      }
    87
    88      /***
    89       * Returns the associated default expression engine.
    90       *
    91       * @return the associated expression engine
    92       */
    93      public DefaultExpressionEngine getExpressionEngine()
    94      {
    95          return expressionEngine;
    96      }
    97
    98      /***
    99       * Sets the associated expression engine.
    100      *
    101      * @param expressionEngine the expression engine (must not be <b>null</b>)
    102      */
    103     public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
    104     {
    105         if (expressionEngine == null)
    106         {
    107             throw new IllegalArgumentException(
    108                     "Expression engine must not be null!");
    109         }
    110         this.expressionEngine = 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(getExpressionEngine().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(getExpressionEngine().getIndexStart());
    168         keyBuffer.append(index);
    169         keyBuffer.append(getExpressionEngine().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()</code> methods. The semantic is the same as the
    199      * <code>setLength()</code> method of <code>StringBuffer</code>.
    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     /***
    209      * Checks if two <code>ConfigurationKey</code> objects are equal. The
    210      * method can be called with strings or other objects, too.
    211      *
    212      * @param c the object to compare
    213      * @return a flag if both objects are equal
    214      */
    215     public boolean equals(Object c)
    216     {
    217         if (c == null)
    218         {
    219             return false;
    220         }
    221
    222         return keyBuffer.toString().equals(c.toString());
    223     }
    224
    225     /***
    226      * Returns the hash code for this object.
    227      *
    228      * @return the hash code
    229      */
    230     public int hashCode()
    231     {
    232         return String.valueOf(keyBuffer).hashCode();
    233     }
    234
    235     /***
    236      * Returns a string representation of this object. This is the configuration
    237      * key as a plain string.
    238      *
    239      * @return a string for this object
    240      */
    241     public String toString()
    242     {
    243         return keyBuffer.toString();
    244     }
    245
    246     /***
    247      * Tests if the specified key represents an attribute according to the
    248      * current expression engine.
    249      *
    250      * @param key the key to be checked
    251      * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
    252      */
    253     public boolean isAttributeKey(String key)
    254     {
    255         if (key == null)
    256         {
    257             return false;
    258         }
    259
    260         return key.startsWith(getExpressionEngine().getAttributeStart())
    261                 && (getExpressionEngine().getAttributeEnd() == null || key
    262                         .endsWith(getExpressionEngine().getAttributeEnd()));
    263     }
    264
    265     /***
    266      * Decorates the given key so that it represents an attribute. Adds special
    267      * start and end markers. The passed in string will be modified only if does
    268      * not already represent an attribute.
    269      *
    270      * @param key the key to be decorated
    271      * @return the decorated attribute key
    272      */
    273     public String constructAttributeKey(String key)
    274     {
    275         if (key == null)
    276         {
    277             return StringUtils.EMPTY;
    278         }
    279         if (isAttributeKey(key))
    280         {
    281             return key;
    282         }
    283         else
    284         {
    285             StringBuffer buf = new StringBuffer();
    286             buf.append(getExpressionEngine().getAttributeStart()).append(key);
    287             if (getExpressionEngine().getAttributeEnd() != null)
    288             {
    289                 buf.append(getExpressionEngine().getAttributeEnd());
    290             }
    291             return buf.toString();
    292         }
    293     }
    294
    295     /***
    296      * Extracts the name of the attribute from the given attribute key. This
    297      * method removes the attribute markers - if any - from the specified key.
    298      *
    299      * @param key the attribute key
    300      * @return the name of the corresponding attribute
    301      */
    302     public String attributeName(String key)
    303     {
    304         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
    305     }
    306
    307     /***
    308      * Removes leading property delimiters from the specified key.
    309      *
    310      * @param key the key
    311      * @return the key with removed leading property delimiters
    312      */
    313     public String trimLeft(String key)
    314     {
    315         if (key == null)
    316         {
    317             return StringUtils.EMPTY;
    318         }
    319         else
    320         {
    321             String result = key;
    322             while (hasLeadingDelimiter(result))
    323             {
    324                 result = result.substring(getExpressionEngine()
    325                         .getPropertyDelimiter().length());
    326             }
    327             return result;
    328         }
    329     }
    330
    331     /***
    332      * Removes trailing property delimiters from the specified key.
    333      *
    334      * @param key the key
    335      * @return the key with removed trailing property delimiters
    336      */
    337     public String trimRight(String key)
    338     {
    339         if (key == null)
    340         {
    341             return StringUtils.EMPTY;
    342         }
    343         else
    344         {
    345             String result = key;
    346             while (hasTrailingDelimiter(result))
    347             {
    348                 result = result
    349                         .substring(0, result.length()
    350                                 - getExpressionEngine().getPropertyDelimiter()
    351                                         .length());
    352             }
    353             return result;
    354         }
    355     }
    356
    357     /***
    358      * Removes delimiters at the beginning and the end of the specified key.
    359      *
    360      * @param key the key
    361      * @return the key with removed property delimiters
    362      */
    363     public String trim(String key)
    364     {
    365         return trimRight(trimLeft(key));
    366     }
    367
    368     /***
    369      * Returns an iterator for iterating over the single components of this
    370      * configuration key.
    371      *
    372      * @return an iterator for this key
    373      */
    374     public KeyIterator iterator()
    375     {
    376         return new KeyIterator();
    377     }
    378
    379     /***
    380      * Helper method that checks if the specified key ends with a property
    381      * delimiter.
    382      *
    383      * @param key the key to check
    384      * @return a flag if there is a trailing delimiter
    385      */
    386     private boolean hasTrailingDelimiter(String key)
    387     {
    388         return key.endsWith(getExpressionEngine().getPropertyDelimiter())
    389                 && (getExpressionEngine().getEscapedDelimiter() == null || !key
    390                         .endsWith(getExpressionEngine().getEscapedDelimiter()));
    391     }
    392
    393     /***
    394      * Helper method that checks if the specified key starts with a property
    395      * delimiter.
    396      *
    397      * @param key the key to check
    398      * @return a flag if there is a leading delimiter
    399      */
    400     private boolean hasLeadingDelimiter(String key)
    401     {
    402         return key.startsWith(getExpressionEngine().getPropertyDelimiter())
    403                 && (getExpressionEngine().getEscapedDelimiter() == null || !key
    404                         .startsWith(getExpressionEngine().getEscapedDelimiter()));
    405     }
    406
    407     /***
    408      * Helper method for removing attribute markers from a key.
    409      *
    410      * @param key the key
    411      * @return the key with removed attribute markers
    412      */
    413     private String removeAttributeMarkers(String key)
    414     {
    415         return key
    416                 .substring(
    417                         getExpressionEngine().getAttributeStart().length(),
    418                         key.length()
    419                                 - ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
    420                                         .getAttributeEnd().length()
    421                                         : 0));
    422     }
    423
    424     /***
    425      * Unescapes the delimiters in the specified string.
    426      *
    427      * @param key the key to be unescaped
    428      * @return the unescaped key
    429      */
    430     private String unescapeDelimiters(String key)
    431     {
    432         return (getExpressionEngine().getEscapedDelimiter() == null) ? key
    433                 : StringUtils.replace(key, getExpressionEngine()
    434                         .getEscapedDelimiter(), getExpressionEngine()
    435                         .getPropertyDelimiter());
    436     }
    437
    438     /***
    439      * Escapes the delimiters in the specified string.
    440      *
    441      * @param key the key to be escaped
    442      * @return the escaped key
    443      */
    444     private String escapeDelimiters(String key)
    445     {
    446         return (getExpressionEngine().getEscapedDelimiter() == null || key
    447                 .indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
    448                 : StringUtils.replace(key, getExpressionEngine()
    449                         .getPropertyDelimiter(), getExpressionEngine()
    450                         .getEscapedDelimiter());
    451     }
    452
    453     /***
    454      * A specialized iterator class for tokenizing a configuration key. This
    455      * class implements the normal iterator interface. In addition it provides
    456      * some specific methods for configuration keys.
    457      */
    458     public class KeyIterator implements Iterator, Cloneable
    459     {
    460         /*** Stores the current key name. */
    461         private String current;
    462
    463         /*** Stores the start index of the actual token. */
    464         private int startIndex;
    465
    466         /*** Stores the end index of the actual token. */
    467         private int endIndex;
    468
    469         /*** Stores the index of the actual property if there is one. */
    470         private int indexValue;
    471
    472         /*** Stores a flag if the actual property has an index. */
    473         private boolean hasIndex;
    474
    475         /*** Stores a flag if the actual property is an attribute. */
    476         private boolean attribute;
    477
    478         /***
    479          * Returns the next key part of this configuration key. This is a short
    480          * form of <code>nextKey(false)</code>.
    481          *
    482          * @return the next key part
    483          */
    484         public String nextKey()
    485         {
    486             return nextKey(false);
    487         }
    488
    489         /***
    490          * Returns the next key part of this configuration key. The boolean
    491          * parameter indicates wheter a decorated key should be returned. This
    492          * affects only attribute keys: if the parameter is <b>false</b>, the
    493          * attribute markers are stripped from the key; if it is <b>true</b>,
    494          * they remain.
    495          *
    496          * @param decorated a flag if the decorated key is to be returned
    497          * @return the next key part
    498          */
    499         public String nextKey(boolean decorated)
    500         {
    501             if (!hasNext())
    502             {
    503                 throw new NoSuchElementException("No more key parts!");
    504             }
    505
    506             hasIndex = false;
    507             indexValue = -1;
    508             String key = findNextIndices();
    509
    510             current = key;
    511             hasIndex = checkIndex(key);
    512             attribute = checkAttribute(current);
    513
    514             return currentKey(decorated);
    515         }
    516
    517         /***
    518          * Checks if there is a next element.
    519          *
    520          * @return a flag if there is a next element
    521          */
    522         public boolean hasNext()
    523         {
    524             return endIndex < keyBuffer.length();
    525         }
    526
    527         /***
    528          * Returns the next object in the iteration.
    529          *
    530          * @return the next object
    531          */
    532         public Object next()
    533         {
    534             return nextKey();
    535         }
    536
    537         /***
    538          * Removes the current object in the iteration. This method is not
    539          * supported by this iterator type, so an exception is thrown.
    540          */
    541         public void remove()
    542         {
    543             throw new UnsupportedOperationException("Remove not supported!");
    544         }
    545
    546         /***
    547          * Returns the current key of the iteration (without skipping to the
    548          * next element). This is the same key the previous <code>next()</code>
    549          * call had returned. (Short form of <code>currentKey(false)</code>.
    550          *
    551          * @return the current key
    552          */
    553         public String currentKey()
    554         {
    555             return currentKey(false);
    556         }
    557
    558         /***
    559          * Returns the current key of the iteration (without skipping to the
    560          * next element). The boolean parameter indicates wheter a decorated key
    561          * should be returned. This affects only attribute keys: if the
    562          * parameter is <b>false</b>, the attribute markers are stripped from
    563          * the key; if it is <b>true</b>, they remain.
    564          *
    565          * @param decorated a flag if the decorated key is to be returned
    566          * @return the current key
    567          */
    568         public String currentKey(boolean decorated)
    569         {
    570             return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
    571                     : current;
    572         }
    573
    574         /***
    575          * Returns a flag if the current key is an attribute. This method can be
    576          * called after <code>next()</code>.
    577          *
    578          * @return a flag if the current key is an attribute
    579          */
    580         public boolean isAttribute()
    581         {
    582             // if attribute emulation mode is active, the last part of a key is
    583             // always an attribute key, too
    584             return attribute || (isAttributeEmulatingMode() && !hasNext());
    585         }
    586
    587         /***
    588          * Returns a flag whether the current key refers to a property (i.e. is
    589          * no special attribute key). Usually this method will return the
    590          * opposite of <code>isAttribute()</code>, but if the delimiters for
    591          * normal properties and attributes are set to the same string, it is
    592          * possible that both methods return <b>true</b>.
    593          *
    594          * @return a flag if the current key is a property key
    595          * @see #isAttribute()
    596          */
    597         public boolean isPropertyKey()
    598         {
    599             return !attribute;
    600         }
    601
    602         /***
    603          * Returns the index value of the current key. If the current key does
    604          * not have an index, return value is -1. This method can be called
    605          * after <code>next()</code>.
    606          *
    607          * @return the index value of the current key
    608          */
    609         public int getIndex()
    610         {
    611             return indexValue;
    612         }
    613
    614         /***
    615          * Returns a flag if the current key has an associated index. This
    616          * method can be called after <code>next()</code>.
    617          *
    618          * @return a flag if the current key has an index
    619          */
    620         public boolean hasIndex()
    621         {
    622             return hasIndex;
    623         }
    624
    625         /***
    626          * Creates a clone of this object.
    627          *
    628          * @return a clone of this object
    629          */
    630         public Object clone()
    631         {
    632             try
    633             {
    634                 return super.clone();
    635             }
    636             catch (CloneNotSupportedException cex)
    637             {
    638                 // should not happen
    639                 return null;
    640             }
    641         }
    642
    643         /***
    644          * Helper method for determining the next indices.
    645          *
    646          * @return the next key part
    647          */
    648         private String findNextIndices()
    649         {
    650             startIndex = endIndex;
    651             // skip empty names
    652             while (startIndex < length()
    653                     && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
    654             {
    655                 startIndex += getExpressionEngine().getPropertyDelimiter()
    656                         .length();
    657             }
    658
    659             // Key ends with a delimiter?
    660             if (startIndex >= length())
    661             {
    662                 endIndex = length();
    663                 startIndex = endIndex - 1;
    664                 return keyBuffer.substring(startIndex, endIndex);
    665             }
    666             else
    667             {
    668                 return nextKeyPart();
    669             }
    670         }
    671
    672         /***
    673          * Helper method for extracting the next key part. Takes escaping of
    674          * delimiter characters into account.
    675          *
    676          * @return the next key part
    677          */
    678         private String nextKeyPart()
    679         {
    680             int attrIdx = keyBuffer.toString().indexOf(
    681                     getExpressionEngine().getAttributeStart(), startIndex);
    682             if (attrIdx < 0 || attrIdx == startIndex)
    683             {
    684                 attrIdx = length();
    685             }
    686
    687             int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
    688                     attrIdx);
    689             if (delIdx < 0)
    690             {
    691                 delIdx = attrIdx;
    692             }
    693
    694             endIndex = Math.min(attrIdx, delIdx);
    695             return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
    696         }
    697
    698         /***
    699          * Searches the next unescaped delimiter from the given position.
    700          *
    701          * @param key the key
    702          * @param pos the start position
    703          * @param endPos the end position
    704          * @return the position of the next delimiter or -1 if there is none
    705          */
    706         private int nextDelimiterPos(String key, int pos, int endPos)
    707         {
    708             int delimiterPos = pos;
    709             boolean found = false;
    710
    711             do
    712             {
    713                 delimiterPos = key.indexOf(getExpressionEngine()
    714                         .getPropertyDelimiter(), delimiterPos);
    715                 if (delimiterPos < 0 || delimiterPos >= endPos)
    716                 {
    717                     return -1;
    718                 }
    719                 int escapePos = escapedPosition(key, delimiterPos);
    720                 if (escapePos < 0)
    721                 {
    722                     found = true;
    723                 }
    724                 else
    725                 {
    726                     delimiterPos = escapePos;
    727                 }
    728             }
    729             while (!found);
    730
    731             return delimiterPos;
    732         }
    733
    734         /***
    735          * Checks if a delimiter at the specified position is escaped. If this
    736          * is the case, the next valid search position will be returned.
    737          * Otherwise the return value is -1.
    738          *
    739          * @param key the key to check
    740          * @param pos the position where a delimiter was found
    741          * @return information about escaped delimiters
    742          */
    743         private int escapedPosition(String key, int pos)
    744         {
    745             if (getExpressionEngine().getEscapedDelimiter() == null)
    746             {
    747                 // nothing to escape
    748                 return -1;
    749             }
    750             int escapeOffset = escapeOffset();
    751             if (escapeOffset < 0 || escapeOffset > pos)
    752             {
    753                 // No escaping possible at this position
    754                 return -1;
    755             }
    756
    757             int escapePos = key.indexOf(getExpressionEngine()
    758                     .getEscapedDelimiter(), pos - escapeOffset);
    759             if (escapePos <= pos && escapePos >= 0)
    760             {
    761                 // The found delimiter is escaped. Next valid search position
    762                 // is behind the escaped delimiter.
    763                 return escapePos
    764                         + getExpressionEngine().getEscapedDelimiter().length();
    765             }
    766             else
    767             {
    768                 return -1;
    769             }
    770         }
    771
    772         /***
    773          * Determines the relative offset of an escaped delimiter in relation to
    774          * a delimiter. Depending on the used delimiter and escaped delimiter
    775          * tokens the position where to search for an escaped delimiter is
    776          * different. If, for instance, the dot character (&quot;.&quot;) is
    777          * used as delimiter, and a doubled dot (&quot;..&quot;) as escaped
    778          * delimiter, the escaped delimiter starts at the same position as the
    779          * delimiter. If the token &quot;\.&quot; was used, it would start one
    780          * character before the delimiter because the delimiter character
    781          * &quot;.&quot; is the second character in the escaped delimiter
    782          * string. This relation will be determined by this method. For this to
    783          * work the delimiter string must be contained in the escaped delimiter
    784          * string.
    785          *
    786          * @return the relative offset of the escaped delimiter in relation to a
    787          * delimiter
    788          */
    789         private int escapeOffset()
    790         {
    791             return getExpressionEngine().getEscapedDelimiter().indexOf(
    792                     getExpressionEngine().getPropertyDelimiter());
    793         }
    794
    795         /***
    796          * Helper method for checking if the passed key is an attribute. If this
    797          * is the case, the internal fields will be set.
    798          *
    799          * @param key the key to be checked
    800          * @return a flag if the key is an attribute
    801          */
    802         private boolean checkAttribute(String key)
    803         {
    804             if (isAttributeKey(key))
    805             {
    806                 current = removeAttributeMarkers(key);
    807                 return true;
    808             }
    809             else
    810             {
    811                 return false;
    812             }
    813         }
    814
    815         /***
    816          * Helper method for checking if the passed key contains an index. If
    817          * this is the case, internal fields will be set.
    818          *
    819          * @param key the key to be checked
    820          * @return a flag if an index is defined
    821          */
    822         private boolean checkIndex(String key)
    823         {
    824             boolean result = false;
    825
    826             int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
    827             if (idx > 0)
    828             {
    829                 int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
    830                         idx);
    831
    832                 if (endidx > idx + 1)
    833                 {
    834                     indexValue = Integer.parseInt(key
    835                             .substring(idx + 1, endidx));
    836                     current = key.substring(0, idx);
    837                     result = true;
    838                 }
    839             }
    840
    841             return result;
    842         }
    843
    844         /***
    845          * Returns a flag whether attributes are marked the same way as normal
    846          * property keys. We call this the &quot;attribute emulating mode&quot;.
    847          * When navigating through node hierarchies it might be convenient to
    848          * treat attributes the same way than other child nodes, so an
    849          * expression engine supports to set the attribute markers to the same
    850          * value than the property delimiter. If this is the case, some special
    851          * checks have to be performed.
    852          *
    853          * @return a flag if attributes and normal property keys are treated the
    854          * same way
    855          */
    856         private boolean isAttributeEmulatingMode()
    857         {
    858             return getExpressionEngine().getAttributeEnd() == null
    859                     && StringUtils.equals(getExpressionEngine()
    860                             .getPropertyDelimiter(), getExpressionEngine()
    861                             .getAttributeStart());
    862         }
    863     }
    864 }