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