Coverage Report - org.apache.commons.configuration.tree.DefaultConfigurationKey
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultConfigurationKey
98%
68/69
96%
56/58
2,511
DefaultConfigurationKey$KeyIterator
93%
72/77
94%
53/56
2,511
 
 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 {@link DefaultExpressionEngine} object.
 28  
  * </p>
 29  
  * <p>
 30  
  * For key creation the class works similar to a StringBuffer: There are several
 31  
  * {@code appendXXXX()} 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  
  * {@link DefaultExpressionEngine}, 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 <a
 50  
  * href="http://commons.apache.org/configuration/team-list.html">Commons
 51  
  * Configuration team</a>
 52  
  * @version $Id: DefaultConfigurationKey.java 1231724 2012-01-15 18:40:31Z oheger $
 53  
  */
 54  2104703
 public class DefaultConfigurationKey
 55  
 {
 56  
     /** Constant for the initial StringBuffer size. */
 57  
     private static final int INITIAL_SIZE = 32;
 58  
 
 59  
     /** Stores a reference to the associated expression engine. */
 60  
     private DefaultExpressionEngine expressionEngine;
 61  
 
 62  
     /** Holds a buffer with the so far created key. */
 63  
     private StringBuilder keyBuffer;
 64  
 
 65  
     /**
 66  
      * Creates a new instance of {@code DefaultConfigurationKey} and sets
 67  
      * the associated expression engine.
 68  
      *
 69  
      * @param engine the expression engine
 70  
      */
 71  
     public DefaultConfigurationKey(DefaultExpressionEngine engine)
 72  35
     {
 73  35
         keyBuffer = new StringBuilder(INITIAL_SIZE);
 74  35
         setExpressionEngine(engine);
 75  35
     }
 76  
 
 77  
     /**
 78  
      * Creates a new instance of {@code DefaultConfigurationKey} and sets
 79  
      * the associated expression engine and an initial key.
 80  
      *
 81  
      * @param engine the expression engine
 82  
      * @param key the key to be wrapped
 83  
      */
 84  
     public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
 85  237797
     {
 86  237799
         setExpressionEngine(engine);
 87  237741
         keyBuffer = new StringBuilder(trim(key));
 88  237757
     }
 89  
 
 90  
     /**
 91  
      * Returns the associated default expression engine.
 92  
      *
 93  
      * @return the associated expression engine
 94  
      */
 95  
     public DefaultExpressionEngine getExpressionEngine()
 96  
     {
 97  2917508
         return expressionEngine;
 98  
     }
 99  
 
 100  
     /**
 101  
      * Sets the associated expression engine.
 102  
      *
 103  
      * @param expressionEngine the expression engine (must not be <b>null</b>)
 104  
      */
 105  
     public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
 106  
     {
 107  237762
         if (expressionEngine == null)
 108  
         {
 109  1
             throw new IllegalArgumentException(
 110  
                     "Expression engine must not be null!");
 111  
         }
 112  237738
         this.expressionEngine = expressionEngine;
 113  237772
     }
 114  
 
 115  
     /**
 116  
      * Appends the name of a property to this key. If necessary, a property
 117  
      * delimiter will be added. If the boolean argument is set to <b>true</b>,
 118  
      * property delimiters contained in the property name will be escaped.
 119  
      *
 120  
      * @param property the name of the property to be added
 121  
      * @param escape a flag if property delimiters in the passed in property name
 122  
      * should be escaped
 123  
      * @return a reference to this object
 124  
      */
 125  
     public DefaultConfigurationKey append(String property, boolean escape)
 126  
     {
 127  
         String key;
 128  2301
         if (escape && property != null)
 129  
         {
 130  2237
             key = escapeDelimiters(property);
 131  
         }
 132  
         else
 133  
         {
 134  64
             key = property;
 135  
         }
 136  2301
         key = trim(key);
 137  
 
 138  2301
         if (keyBuffer.length() > 0 && !isAttributeKey(property)
 139  
                 && key.length() > 0)
 140  
         {
 141  1616
             keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
 142  
         }
 143  
 
 144  2301
         keyBuffer.append(key);
 145  2301
         return this;
 146  
     }
 147  
 
 148  
     /**
 149  
      * Appends the name of a property to this key. If necessary, a property
 150  
      * delimiter will be added. Property delimiters in the given string will not
 151  
      * be escaped.
 152  
      *
 153  
      * @param property the name of the property to be added
 154  
      * @return a reference to this object
 155  
      */
 156  
     public DefaultConfigurationKey append(String property)
 157  
     {
 158  63
         return append(property, false);
 159  
     }
 160  
 
 161  
     /**
 162  
      * Appends an index to this configuration key.
 163  
      *
 164  
      * @param index the index to be appended
 165  
      * @return a reference to this object
 166  
      */
 167  
     public DefaultConfigurationKey appendIndex(int index)
 168  
     {
 169  15
         keyBuffer.append(getExpressionEngine().getIndexStart());
 170  15
         keyBuffer.append(index);
 171  15
         keyBuffer.append(getExpressionEngine().getIndexEnd());
 172  15
         return this;
 173  
     }
 174  
 
 175  
     /**
 176  
      * Appends an attribute to this configuration key.
 177  
      *
 178  
      * @param attr the name of the attribute to be appended
 179  
      * @return a reference to this object
 180  
      */
 181  
     public DefaultConfigurationKey appendAttribute(String attr)
 182  
     {
 183  809
         keyBuffer.append(constructAttributeKey(attr));
 184  809
         return this;
 185  
     }
 186  
 
 187  
     /**
 188  
      * Returns the actual length of this configuration key.
 189  
      *
 190  
      * @return the length of this key
 191  
      */
 192  
     public int length()
 193  
     {
 194  778685
         return keyBuffer.length();
 195  
     }
 196  
 
 197  
     /**
 198  
      * Sets the new length of this configuration key. With this method it is
 199  
      * possible to truncate the key, e.g. to return to a state prior calling
 200  
      * some {@code append()} methods. The semantic is the same as the
 201  
      * {@code setLength()} method of {@code StringBuilder}.
 202  
      *
 203  
      * @param len the new length of the key
 204  
      */
 205  
     public void setLength(int len)
 206  
     {
 207  1
         keyBuffer.setLength(len);
 208  1
     }
 209  
 
 210  
     /**
 211  
      * Checks if two {@code ConfigurationKey} objects are equal. The
 212  
      * method can be called with strings or other objects, too.
 213  
      *
 214  
      * @param c the object to compare
 215  
      * @return a flag if both objects are equal
 216  
      */
 217  
     @Override
 218  
     public boolean equals(Object c)
 219  
     {
 220  6
         if (c == null)
 221  
         {
 222  1
             return false;
 223  
         }
 224  
 
 225  5
         return keyBuffer.toString().equals(c.toString());
 226  
     }
 227  
 
 228  
     /**
 229  
      * Returns the hash code for this object.
 230  
      *
 231  
      * @return the hash code
 232  
      */
 233  
     @Override
 234  
     public int hashCode()
 235  
     {
 236  2
         return String.valueOf(keyBuffer).hashCode();
 237  
     }
 238  
 
 239  
     /**
 240  
      * Returns a string representation of this object. This is the configuration
 241  
      * key as a plain string.
 242  
      *
 243  
      * @return a string for this object
 244  
      */
 245  
     @Override
 246  
     public String toString()
 247  
     {
 248  3069
         return keyBuffer.toString();
 249  
     }
 250  
 
 251  
     /**
 252  
      * Tests if the specified key represents an attribute according to the
 253  
      * current expression engine.
 254  
      *
 255  
      * @param key the key to be checked
 256  
      * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
 257  
      */
 258  
     public boolean isAttributeKey(String key)
 259  
     {
 260  256740
         if (key == null)
 261  
         {
 262  4
             return false;
 263  
         }
 264  
 
 265  256766
         return key.startsWith(getExpressionEngine().getAttributeStart())
 266  
                 && (getExpressionEngine().getAttributeEnd() == null || key
 267  
                         .endsWith(getExpressionEngine().getAttributeEnd()));
 268  
     }
 269  
 
 270  
     /**
 271  
      * Decorates the given key so that it represents an attribute. Adds special
 272  
      * start and end markers. The passed in string will be modified only if does
 273  
      * not already represent an attribute.
 274  
      *
 275  
      * @param key the key to be decorated
 276  
      * @return the decorated attribute key
 277  
      */
 278  
     public String constructAttributeKey(String key)
 279  
     {
 280  816
         if (key == null)
 281  
         {
 282  2
             return StringUtils.EMPTY;
 283  
         }
 284  814
         if (isAttributeKey(key))
 285  
         {
 286  3
             return key;
 287  
         }
 288  
         else
 289  
         {
 290  811
             StringBuilder buf = new StringBuilder();
 291  811
             buf.append(getExpressionEngine().getAttributeStart()).append(key);
 292  811
             if (getExpressionEngine().getAttributeEnd() != null)
 293  
             {
 294  808
                 buf.append(getExpressionEngine().getAttributeEnd());
 295  
             }
 296  811
             return buf.toString();
 297  
         }
 298  
     }
 299  
 
 300  
     /**
 301  
      * Extracts the name of the attribute from the given attribute key. This
 302  
      * method removes the attribute markers - if any - from the specified key.
 303  
      *
 304  
      * @param key the attribute key
 305  
      * @return the name of the corresponding attribute
 306  
      */
 307  
     public String attributeName(String key)
 308  
     {
 309  3
         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
 310  
     }
 311  
 
 312  
     /**
 313  
      * Removes leading property delimiters from the specified key.
 314  
      *
 315  
      * @param key the key
 316  
      * @return the key with removed leading property delimiters
 317  
      */
 318  
     public String trimLeft(String key)
 319  
     {
 320  239986
         if (key == null)
 321  
         {
 322  388
             return StringUtils.EMPTY;
 323  
         }
 324  
         else
 325  
         {
 326  239617
             String result = key;
 327  239631
             while (hasLeadingDelimiter(result))
 328  
             {
 329  16
                 result = result.substring(getExpressionEngine()
 330  
                         .getPropertyDelimiter().length());
 331  
             }
 332  239633
             return result;
 333  
         }
 334  
     }
 335  
 
 336  
     /**
 337  
      * Removes trailing property delimiters from the specified key.
 338  
      *
 339  
      * @param key the key
 340  
      * @return the key with removed trailing property delimiters
 341  
      */
 342  
     public String trimRight(String key)
 343  
     {
 344  239932
         if (key == null)
 345  
         {
 346  0
             return StringUtils.EMPTY;
 347  
         }
 348  
         else
 349  
         {
 350  239985
             String result = key;
 351  240042
             while (hasTrailingDelimiter(result))
 352  
             {
 353  14
                 result = result
 354  
                         .substring(0, result.length()
 355  
                                 - getExpressionEngine().getPropertyDelimiter()
 356  
                                         .length());
 357  
             }
 358  240075
             return result;
 359  
         }
 360  
     }
 361  
 
 362  
     /**
 363  
      * Removes delimiters at the beginning and the end of the specified key.
 364  
      *
 365  
      * @param key the key
 366  
      * @return the key with removed property delimiters
 367  
      */
 368  
     public String trim(String key)
 369  
     {
 370  240006
         return trimRight(trimLeft(key));
 371  
     }
 372  
 
 373  
     /**
 374  
      * Returns an iterator for iterating over the single components of this
 375  
      * configuration key.
 376  
      *
 377  
      * @return an iterator for this key
 378  
      */
 379  
     public KeyIterator iterator()
 380  
     {
 381  234735
         return new KeyIterator();
 382  
     }
 383  
 
 384  
     /**
 385  
      * Helper method that checks if the specified key ends with a property
 386  
      * delimiter.
 387  
      *
 388  
      * @param key the key to check
 389  
      * @return a flag if there is a trailing delimiter
 390  
      */
 391  
     private boolean hasTrailingDelimiter(String key)
 392  
     {
 393  239942
         return key.endsWith(getExpressionEngine().getPropertyDelimiter())
 394  
                 && (getExpressionEngine().getEscapedDelimiter() == null || !key
 395  
                         .endsWith(getExpressionEngine().getEscapedDelimiter()));
 396  
     }
 397  
 
 398  
     /**
 399  
      * Helper method that checks if the specified key starts with a property
 400  
      * delimiter.
 401  
      *
 402  
      * @param key the key to check
 403  
      * @return a flag if there is a leading delimiter
 404  
      */
 405  
     private boolean hasLeadingDelimiter(String key)
 406  
     {
 407  511859
         return key.startsWith(getExpressionEngine().getPropertyDelimiter())
 408  
                 && (getExpressionEngine().getEscapedDelimiter() == null || !key
 409  
                         .startsWith(getExpressionEngine().getEscapedDelimiter()));
 410  
     }
 411  
 
 412  
     /**
 413  
      * Helper method for removing attribute markers from a key.
 414  
      *
 415  
      * @param key the key
 416  
      * @return the key with removed attribute markers
 417  
      */
 418  
     private String removeAttributeMarkers(String key)
 419  
     {
 420  4129
         return key
 421  
                 .substring(
 422  
                         getExpressionEngine().getAttributeStart().length(),
 423  
                         key.length()
 424  
                                 - ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
 425  
                                         .getAttributeEnd().length()
 426  
                                         : 0));
 427  
     }
 428  
 
 429  
     /**
 430  
      * Unescapes the delimiters in the specified string.
 431  
      *
 432  
      * @param key the key to be unescaped
 433  
      * @return the unescaped key
 434  
      */
 435  
     private String unescapeDelimiters(String key)
 436  
     {
 437  254308
         return (getExpressionEngine().getEscapedDelimiter() == null) ? key
 438  
                 : StringUtils.replace(key, getExpressionEngine()
 439  
                         .getEscapedDelimiter(), getExpressionEngine()
 440  
                         .getPropertyDelimiter());
 441  
     }
 442  
 
 443  
     /**
 444  
      * Escapes the delimiters in the specified string.
 445  
      *
 446  
      * @param key the key to be escaped
 447  
      * @return the escaped key
 448  
      */
 449  
     private String escapeDelimiters(String key)
 450  
     {
 451  2237
         return (getExpressionEngine().getEscapedDelimiter() == null || key
 452  
                 .indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
 453  
                 : StringUtils.replace(key, getExpressionEngine()
 454  
                         .getPropertyDelimiter(), getExpressionEngine()
 455  
                         .getEscapedDelimiter());
 456  
     }
 457  
 
 458  
     /**
 459  
      * A specialized iterator class for tokenizing a configuration key. This
 460  
      * class implements the normal iterator interface. In addition it provides
 461  
      * some specific methods for configuration keys.
 462  
      */
 463  234663
     public class KeyIterator implements Iterator<Object>, Cloneable
 464  
     {
 465  
         /** Stores the current key name. */
 466  
         private String current;
 467  
 
 468  
         /** Stores the start index of the actual token. */
 469  
         private int startIndex;
 470  
 
 471  
         /** Stores the end index of the actual token. */
 472  
         private int endIndex;
 473  
 
 474  
         /** Stores the index of the actual property if there is one. */
 475  
         private int indexValue;
 476  
 
 477  
         /** Stores a flag if the actual property has an index. */
 478  
         private boolean hasIndex;
 479  
 
 480  
         /** Stores a flag if the actual property is an attribute. */
 481  
         private boolean attribute;
 482  
 
 483  
         /**
 484  
          * Returns the next key part of this configuration key. This is a short
 485  
          * form of {@code nextKey(false)}.
 486  
          *
 487  
          * @return the next key part
 488  
          */
 489  
         public String nextKey()
 490  
         {
 491  3179
             return nextKey(false);
 492  
         }
 493  
 
 494  
         /**
 495  
          * Returns the next key part of this configuration key. The boolean
 496  
          * parameter indicates wheter a decorated key should be returned. This
 497  
          * affects only attribute keys: if the parameter is <b>false</b>, the
 498  
          * attribute markers are stripped from the key; if it is <b>true</b>,
 499  
          * they remain.
 500  
          *
 501  
          * @param decorated a flag if the decorated key is to be returned
 502  
          * @return the next key part
 503  
          */
 504  
         public String nextKey(boolean decorated)
 505  
         {
 506  254259
             if (!hasNext())
 507  
             {
 508  1
                 throw new NoSuchElementException("No more key parts!");
 509  
             }
 510  
 
 511  254446
             hasIndex = false;
 512  254449
             indexValue = -1;
 513  254439
             String key = findNextIndices();
 514  
 
 515  254309
             current = key;
 516  254397
             hasIndex = checkIndex(key);
 517  254352
             attribute = checkAttribute(current);
 518  
 
 519  254335
             return currentKey(decorated);
 520  
         }
 521  
 
 522  
         /**
 523  
          * Checks if there is a next element.
 524  
          *
 525  
          * @return a flag if there is a next element
 526  
          */
 527  
         public boolean hasNext()
 528  
         {
 529  538650
             return endIndex < keyBuffer.length();
 530  
         }
 531  
 
 532  
         /**
 533  
          * Returns the next object in the iteration.
 534  
          *
 535  
          * @return the next object
 536  
          */
 537  
         public Object next()
 538  
         {
 539  3144
             return nextKey();
 540  
         }
 541  
 
 542  
         /**
 543  
          * Removes the current object in the iteration. This method is not
 544  
          * supported by this iterator type, so an exception is thrown.
 545  
          */
 546  
         public void remove()
 547  
         {
 548  1
             throw new UnsupportedOperationException("Remove not supported!");
 549  
         }
 550  
 
 551  
         /**
 552  
          * Returns the current key of the iteration (without skipping to the
 553  
          * next element). This is the same key the previous {@code next()}
 554  
          * call had returned. (Short form of {@code currentKey(false)}.
 555  
          *
 556  
          * @return the current key
 557  
          */
 558  
         public String currentKey()
 559  
         {
 560  13294
             return currentKey(false);
 561  
         }
 562  
 
 563  
         /**
 564  
          * Returns the current key of the iteration (without skipping to the
 565  
          * next element). The boolean parameter indicates wheter a decorated key
 566  
          * should be returned. This affects only attribute keys: if the
 567  
          * parameter is <b>false</b>, the attribute markers are stripped from
 568  
          * the key; if it is <b>true</b>, they remain.
 569  
          *
 570  
          * @param decorated a flag if the decorated key is to be returned
 571  
          * @return the current key
 572  
          */
 573  
         public String currentKey(boolean decorated)
 574  
         {
 575  267560
             return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
 576  
                     : current;
 577  
         }
 578  
 
 579  
         /**
 580  
          * Returns a flag if the current key is an attribute. This method can be
 581  
          * called after {@code next()}.
 582  
          *
 583  
          * @return a flag if the current key is an attribute
 584  
          */
 585  
         public boolean isAttribute()
 586  
         {
 587  
             // if attribute emulation mode is active, the last part of a key is
 588  
             // always an attribute key, too
 589  232867
             return attribute || (isAttributeEmulatingMode() && !hasNext());
 590  
         }
 591  
 
 592  
         /**
 593  
          * Returns a flag whether the current key refers to a property (i.e. is
 594  
          * no special attribute key). Usually this method will return the
 595  
          * opposite of {@code isAttribute()}, but if the delimiters for
 596  
          * normal properties and attributes are set to the same string, it is
 597  
          * possible that both methods return <b>true</b>.
 598  
          *
 599  
          * @return a flag if the current key is a property key
 600  
          * @see #isAttribute()
 601  
          */
 602  
         public boolean isPropertyKey()
 603  
         {
 604  256687
             return !attribute;
 605  
         }
 606  
 
 607  
         /**
 608  
          * Returns the index value of the current key. If the current key does
 609  
          * not have an index, return value is -1. This method can be called
 610  
          * after {@code next()}.
 611  
          *
 612  
          * @return the index value of the current key
 613  
          */
 614  
         public int getIndex()
 615  
         {
 616  1662
             return indexValue;
 617  
         }
 618  
 
 619  
         /**
 620  
          * Returns a flag if the current key has an associated index. This
 621  
          * method can be called after {@code next()}.
 622  
          *
 623  
          * @return a flag if the current key has an index
 624  
          */
 625  
         public boolean hasIndex()
 626  
         {
 627  243413
             return hasIndex;
 628  
         }
 629  
 
 630  
         /**
 631  
          * Creates a clone of this object.
 632  
          *
 633  
          * @return a clone of this object
 634  
          */
 635  
         @Override
 636  
         public Object clone()
 637  
         {
 638  
             try
 639  
             {
 640  18213
                 return super.clone();
 641  
             }
 642  0
             catch (CloneNotSupportedException cex)
 643  
             {
 644  
                 // should not happen
 645  0
                 return null;
 646  
             }
 647  
         }
 648  
 
 649  
         /**
 650  
          * Helper method for determining the next indices.
 651  
          *
 652  
          * @return the next key part
 653  
          */
 654  
         private String findNextIndices()
 655  
         {
 656  254326
             startIndex = endIndex;
 657  
             // skip empty names
 658  272069
             while (startIndex < length()
 659  
                     && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
 660  
             {
 661  17832
                 startIndex += getExpressionEngine().getPropertyDelimiter()
 662  
                         .length();
 663  
             }
 664  
 
 665  
             // Key ends with a delimiter?
 666  254439
             if (startIndex >= length())
 667  
             {
 668  0
                 endIndex = length();
 669  0
                 startIndex = endIndex - 1;
 670  0
                 return keyBuffer.substring(startIndex, endIndex);
 671  
             }
 672  
             else
 673  
             {
 674  254450
                 return nextKeyPart();
 675  
             }
 676  
         }
 677  
 
 678  
         /**
 679  
          * Helper method for extracting the next key part. Takes escaping of
 680  
          * delimiter characters into account.
 681  
          *
 682  
          * @return the next key part
 683  
          */
 684  
         private String nextKeyPart()
 685  
         {
 686  254280
             int attrIdx = keyBuffer.toString().indexOf(
 687  
                     getExpressionEngine().getAttributeStart(), startIndex);
 688  254358
             if (attrIdx < 0 || attrIdx == startIndex)
 689  
             {
 690  252205
                 attrIdx = length();
 691  
             }
 692  
 
 693  254347
             int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
 694  
                     attrIdx);
 695  254373
             if (delIdx < 0)
 696  
             {
 697  237120
                 delIdx = attrIdx;
 698  
             }
 699  
 
 700  254382
             endIndex = Math.min(attrIdx, delIdx);
 701  254414
             return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
 702  
         }
 703  
 
 704  
         /**
 705  
          * Searches the next unescaped delimiter from the given position.
 706  
          *
 707  
          * @param key the key
 708  
          * @param pos the start position
 709  
          * @param endPos the end position
 710  
          * @return the position of the next delimiter or -1 if there is none
 711  
          */
 712  
         private int nextDelimiterPos(String key, int pos, int endPos)
 713  
         {
 714  254342
             int delimiterPos = pos;
 715  254389
             boolean found = false;
 716  
 
 717  
             do
 718  
             {
 719  254533
                 delimiterPos = key.indexOf(getExpressionEngine()
 720  
                         .getPropertyDelimiter(), delimiterPos);
 721  254569
                 if (delimiterPos < 0 || delimiterPos >= endPos)
 722  
                 {
 723  237099
                     return -1;
 724  
                 }
 725  17421
                 int escapePos = escapedPosition(key, delimiterPos);
 726  17421
                 if (escapePos < 0)
 727  
                 {
 728  17237
                     found = true;
 729  
                 }
 730  
                 else
 731  
                 {
 732  184
                     delimiterPos = escapePos;
 733  
                 }
 734  
             }
 735  17421
             while (!found);
 736  
 
 737  17237
             return delimiterPos;
 738  
         }
 739  
 
 740  
         /**
 741  
          * Checks if a delimiter at the specified position is escaped. If this
 742  
          * is the case, the next valid search position will be returned.
 743  
          * Otherwise the return value is -1.
 744  
          *
 745  
          * @param key the key to check
 746  
          * @param pos the position where a delimiter was found
 747  
          * @return information about escaped delimiters
 748  
          */
 749  
         private int escapedPosition(String key, int pos)
 750  
         {
 751  17421
             if (getExpressionEngine().getEscapedDelimiter() == null)
 752  
             {
 753  
                 // nothing to escape
 754  13
                 return -1;
 755  
             }
 756  17408
             int escapeOffset = escapeOffset();
 757  17408
             if (escapeOffset < 0 || escapeOffset > pos)
 758  
             {
 759  
                 // No escaping possible at this position
 760  58
                 return -1;
 761  
             }
 762  
 
 763  17350
             int escapePos = key.indexOf(getExpressionEngine()
 764  
                     .getEscapedDelimiter(), pos - escapeOffset);
 765  17350
             if (escapePos <= pos && escapePos >= 0)
 766  
             {
 767  
                 // The found delimiter is escaped. Next valid search position
 768  
                 // is behind the escaped delimiter.
 769  184
                 return escapePos
 770  
                         + getExpressionEngine().getEscapedDelimiter().length();
 771  
             }
 772  
             else
 773  
             {
 774  17166
                 return -1;
 775  
             }
 776  
         }
 777  
 
 778  
         /**
 779  
          * Determines the relative offset of an escaped delimiter in relation to
 780  
          * a delimiter. Depending on the used delimiter and escaped delimiter
 781  
          * tokens the position where to search for an escaped delimiter is
 782  
          * different. If, for instance, the dot character (&quot;.&quot;) is
 783  
          * used as delimiter, and a doubled dot (&quot;..&quot;) as escaped
 784  
          * delimiter, the escaped delimiter starts at the same position as the
 785  
          * delimiter. If the token &quot;\.&quot; was used, it would start one
 786  
          * character before the delimiter because the delimiter character
 787  
          * &quot;.&quot; is the second character in the escaped delimiter
 788  
          * string. This relation will be determined by this method. For this to
 789  
          * work the delimiter string must be contained in the escaped delimiter
 790  
          * string.
 791  
          *
 792  
          * @return the relative offset of the escaped delimiter in relation to a
 793  
          * delimiter
 794  
          */
 795  
         private int escapeOffset()
 796  
         {
 797  17408
             return getExpressionEngine().getEscapedDelimiter().indexOf(
 798  
                     getExpressionEngine().getPropertyDelimiter());
 799  
         }
 800  
 
 801  
         /**
 802  
          * Helper method for checking if the passed key is an attribute. If this
 803  
          * is the case, the internal fields will be set.
 804  
          *
 805  
          * @param key the key to be checked
 806  
          * @return a flag if the key is an attribute
 807  
          */
 808  
         private boolean checkAttribute(String key)
 809  
         {
 810  254333
             if (isAttributeKey(key))
 811  
             {
 812  4128
                 current = removeAttributeMarkers(key);
 813  4128
                 return true;
 814  
             }
 815  
             else
 816  
             {
 817  250179
                 return false;
 818  
             }
 819  
         }
 820  
 
 821  
         /**
 822  
          * Helper method for checking if the passed key contains an index. If
 823  
          * this is the case, internal fields will be set.
 824  
          *
 825  
          * @param key the key to be checked
 826  
          * @return a flag if an index is defined
 827  
          */
 828  
         private boolean checkIndex(String key)
 829  
         {
 830  254234
             boolean result = false;
 831  
 
 832  
             try
 833  
             {
 834  254291
                 int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
 835  254304
                 if (idx > 0)
 836  
                 {
 837  846
                     int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
 838  
                             idx);
 839  
 
 840  846
                     if (endidx > idx + 1)
 841  
                     {
 842  844
                         indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
 843  841
                         current = key.substring(0, idx);
 844  841
                         result = true;
 845  
                     }
 846  
                 }
 847  
             }
 848  3
             catch (NumberFormatException nfe)
 849  
             {
 850  3
                 result = false;
 851  254332
             }
 852  
 
 853  254263
             return result;
 854  
         }
 855  
 
 856  
         /**
 857  
          * Returns a flag whether attributes are marked the same way as normal
 858  
          * property keys. We call this the &quot;attribute emulating mode&quot;.
 859  
          * When navigating through node hierarchies it might be convenient to
 860  
          * treat attributes the same way than other child nodes, so an
 861  
          * expression engine supports to set the attribute markers to the same
 862  
          * value than the property delimiter. If this is the case, some special
 863  
          * checks have to be performed.
 864  
          *
 865  
          * @return a flag if attributes and normal property keys are treated the
 866  
          * same way
 867  
          */
 868  
         private boolean isAttributeEmulatingMode()
 869  
         {
 870  229104
             return getExpressionEngine().getAttributeEnd() == null
 871  
                     && StringUtils.equals(getExpressionEngine()
 872  
                             .getPropertyDelimiter(), getExpressionEngine()
 873  
                             .getAttributeStart());
 874  
         }
 875  
     }
 876  
 }