Coverage Report - org.apache.commons.configuration.tree.DefaultConfigurationKey
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultConfigurationKey
99%
68/69
100%
19/19
1,956
DefaultConfigurationKey$KeyIterator
95%
70/74
100%
19/19
1,956
 
 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  115002
 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  28
     {
 71  28
         keyBuffer = new StringBuffer(INITIAL_SIZE);
 72  28
         setExpressionEngine(engine);
 73  28
     }
 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  5534
     {
 84  5534
         setExpressionEngine(engine);
 85  5534
         keyBuffer = new StringBuffer(trim(key));
 86  5534
     }
 87  
 
 88  
     /**
 89  
      * Returns the associated default expression engine.
 90  
      *
 91  
      * @return the associated expression engine
 92  
      */
 93  
     public DefaultExpressionEngine getExpressionEngine()
 94  
     {
 95  174131
         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  5563
         if (expressionEngine == null)
 106  
         {
 107  1
             throw new IllegalArgumentException(
 108  
                     "Expression engine must not be null!");
 109  
         }
 110  5562
         this.expressionEngine = expressionEngine;
 111  5562
     }
 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  785
         if (escape && property != null)
 127  
         {
 128  749
             key = escapeDelimiters(property);
 129  
         }
 130  
         else
 131  
         {
 132  36
             key = property;
 133  
         }
 134  785
         key = trim(key);
 135  
 
 136  785
         if (keyBuffer.length() > 0 && !isAttributeKey(property)
 137  
                 && key.length() > 0)
 138  
         {
 139  578
             keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
 140  
         }
 141  
 
 142  785
         keyBuffer.append(key);
 143  785
         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  35
         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  4
         keyBuffer.append(getExpressionEngine().getIndexStart());
 168  4
         keyBuffer.append(index);
 169  4
         keyBuffer.append(getExpressionEngine().getIndexEnd());
 170  4
         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  151
         keyBuffer.append(constructAttributeKey(attr));
 182  151
         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  41381
         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  1
         keyBuffer.setLength(len);
 206  1
     }
 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  6
         if (c == null)
 218  
         {
 219  1
             return false;
 220  
         }
 221  
 
 222  5
         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  2
         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  912
         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  12586
         if (key == null)
 256  
         {
 257  4
             return false;
 258  
         }
 259  
 
 260  12582
         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  158
         if (key == null)
 276  
         {
 277  2
             return StringUtils.EMPTY;
 278  
         }
 279  156
         if (isAttributeKey(key))
 280  
         {
 281  3
             return key;
 282  
         }
 283  
         else
 284  
         {
 285  153
             StringBuffer buf = new StringBuffer();
 286  153
             buf.append(getExpressionEngine().getAttributeStart()).append(key);
 287  153
             if (getExpressionEngine().getAttributeEnd() != null)
 288  
             {
 289  150
                 buf.append(getExpressionEngine().getAttributeEnd());
 290  
             }
 291  153
             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  3
         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  6324
         if (key == null)
 316  
         {
 317  19
             return StringUtils.EMPTY;
 318  
         }
 319  
         else
 320  
         {
 321  6305
             String result = key;
 322  12626
             while (hasLeadingDelimiter(result))
 323  
             {
 324  16
                 result = result.substring(getExpressionEngine()
 325  
                         .getPropertyDelimiter().length());
 326  
             }
 327  6305
             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  6324
         if (key == null)
 340  
         {
 341  0
             return StringUtils.EMPTY;
 342  
         }
 343  
         else
 344  
         {
 345  6324
             String result = key;
 346  12662
             while (hasTrailingDelimiter(result))
 347  
             {
 348  14
                 result = result
 349  
                         .substring(0, result.length()
 350  
                                 - getExpressionEngine().getPropertyDelimiter()
 351  
                                         .length());
 352  
             }
 353  6324
             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  6322
         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  4646
         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  6338
         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  24769
         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  889
         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  11839
         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  749
         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  4646
     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  824
             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  11840
             if (!hasNext())
 502  
             {
 503  1
                 throw new NoSuchElementException("No more key parts!");
 504  
             }
 505  
 
 506  11839
             hasIndex = false;
 507  11839
             indexValue = -1;
 508  11839
             String key = findNextIndices();
 509  
 
 510  11839
             current = key;
 511  11839
             hasIndex = checkIndex(key);
 512  11839
             attribute = checkAttribute(current);
 513  
 
 514  11839
             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  29862
             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  801
             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  1
             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  2481
             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  14325
             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  7193
             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  12445
             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  971
             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  9963
             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  7041
                 return super.clone();
 635  
             }
 636  
             catch (CloneNotSupportedException cex)
 637  
             {
 638  
                 // should not happen
 639  0
                 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  11839
             startIndex = endIndex;
 651  
             // skip empty names
 652  11839
             while (startIndex < length()
 653  18448
                     && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
 654  
             {
 655  6609
                 startIndex += getExpressionEngine().getPropertyDelimiter()
 656  
                         .length();
 657  
             }
 658  
 
 659  
             // Key ends with a delimiter?
 660  11839
             if (startIndex >= length())
 661  
             {
 662  0
                 endIndex = length();
 663  0
                 startIndex = endIndex - 1;
 664  0
                 return keyBuffer.substring(startIndex, endIndex);
 665  
             }
 666  
             else
 667  
             {
 668  11839
                 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  11839
             int attrIdx = keyBuffer.toString().indexOf(
 681  
                     getExpressionEngine().getAttributeStart(), startIndex);
 682  11839
             if (attrIdx < 0 || attrIdx == startIndex)
 683  
             {
 684  11091
                 attrIdx = length();
 685  
             }
 686  
 
 687  11839
             int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
 688  
                     attrIdx);
 689  11839
             if (delIdx < 0)
 690  
             {
 691  5544
                 delIdx = attrIdx;
 692  
             }
 693  
 
 694  11839
             endIndex = Math.min(attrIdx, delIdx);
 695  11839
             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  11839
             int delimiterPos = pos;
 709  11839
             boolean found = false;
 710  
 
 711  
             do
 712  
             {
 713  11927
                 delimiterPos = key.indexOf(getExpressionEngine()
 714  
                         .getPropertyDelimiter(), delimiterPos);
 715  11927
                 if (delimiterPos < 0 || delimiterPos >= endPos)
 716  
                 {
 717  5544
                     return -1;
 718  
                 }
 719  6383
                 int escapePos = escapedPosition(key, delimiterPos);
 720  6383
                 if (escapePos < 0)
 721  
                 {
 722  6295
                     found = true;
 723  
                 }
 724  
                 else
 725  
                 {
 726  88
                     delimiterPos = escapePos;
 727  
                 }
 728  
             }
 729  6383
             while (!found);
 730  
 
 731  6295
             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  6383
             if (getExpressionEngine().getEscapedDelimiter() == null)
 746  
             {
 747  
                 // nothing to escape
 748  13
                 return -1;
 749  
             }
 750  6370
             int escapeOffset = escapeOffset();
 751  6370
             if (escapeOffset < 0 || escapeOffset > pos)
 752  
             {
 753  
                 // No escaping possible at this position
 754  55
                 return -1;
 755  
             }
 756  
 
 757  6315
             int escapePos = key.indexOf(getExpressionEngine()
 758  
                     .getEscapedDelimiter(), pos - escapeOffset);
 759  6315
             if (escapePos <= pos && escapePos >= 0)
 760  
             {
 761  
                 // The found delimiter is escaped. Next valid search position
 762  
                 // is behind the escaped delimiter.
 763  88
                 return escapePos
 764  
                         + getExpressionEngine().getEscapedDelimiter().length();
 765  
             }
 766  
             else
 767  
             {
 768  6227
                 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  6370
             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  11839
             if (isAttributeKey(key))
 805  
             {
 806  888
                 current = removeAttributeMarkers(key);
 807  888
                 return true;
 808  
             }
 809  
             else
 810  
             {
 811  10951
                 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  11839
             boolean result = false;
 825  
 
 826  11839
             int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
 827  11839
             if (idx > 0)
 828  
             {
 829  481
                 int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
 830  
                         idx);
 831  
 
 832  481
                 if (endidx > idx + 1)
 833  
                 {
 834  479
                     indexValue = Integer.parseInt(key
 835  
                             .substring(idx + 1, endidx));
 836  479
                     current = key.substring(0, idx);
 837  479
                     result = true;
 838  
                 }
 839  
             }
 840  
 
 841  11839
             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  6374
             return getExpressionEngine().getAttributeEnd() == null
 859  
                     && StringUtils.equals(getExpressionEngine()
 860  
                             .getPropertyDelimiter(), getExpressionEngine()
 861  
                             .getAttributeStart());
 862  
         }
 863  
     }
 864  
 }