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