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 */ 017 018 package org.apache.commons.configuration; 019 020 import java.io.Serializable; 021 import java.util.Iterator; 022 import java.util.NoSuchElementException; 023 024 /** 025 * <p>A simple class that supports creation of and iteration on complex 026 * configuration keys.</p> 027 * 028 * <p>For key creation the class works similar to a StringBuilder: There are 029 * several {@code appendXXXX()} methods with which single parts 030 * of a key can be constructed. All these methods return a reference to the 031 * actual object so they can be written in a chain. When using this methods 032 * the exact syntax for keys need not be known.</p> 033 * 034 * <p>This class also defines a specialized iterator for configuration keys. 035 * With such an iterator a key can be tokenized into its single parts. For 036 * each part it can be checked whether it has an associated index.</p> 037 * 038 * @author <a 039 * href="http://commons.apache.org/configuration/team-list.html">Commons 040 * Configuration team</a> 041 * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $ 042 * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey} 043 * instead. It is associated with a {@code DefaultExpressionEngine} and thus 044 * can produce correct keys even if key separators have been changed. 045 */ 046 @Deprecated 047 public class ConfigurationKey implements Serializable 048 { 049 /** Constant for a property delimiter.*/ 050 public static final char PROPERTY_DELIMITER = '.'; 051 052 /** Constant for an escaped delimiter. */ 053 public static final String ESCAPED_DELIMITER = 054 String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER); 055 056 /** Constant for an attribute start marker.*/ 057 private static final String ATTRIBUTE_START = "[@"; 058 059 /** Constant for an attribute end marker.*/ 060 private static final String ATTRIBUTE_END = "]"; 061 062 /** Constant for an index start marker.*/ 063 private static final char INDEX_START = '('; 064 065 /** Constant for an index end marker.*/ 066 private static final char INDEX_END = ')'; 067 068 /** Constant for the initial StringBuilder size.*/ 069 private static final int INITIAL_SIZE = 32; 070 071 /** 072 * The serial version ID. 073 */ 074 private static final long serialVersionUID = -4299732083605277656L; 075 076 /** Holds a buffer with the so far created key.*/ 077 private StringBuilder keyBuffer; 078 079 /** 080 * Creates a new, empty instance of {@code ConfigurationKey}. 081 */ 082 public ConfigurationKey() 083 { 084 keyBuffer = new StringBuilder(INITIAL_SIZE); 085 } 086 087 /** 088 * Creates a new instance of {@code ConfigurationKey} and 089 * initializes it with the given key. 090 * 091 * @param key the key as a string 092 */ 093 public ConfigurationKey(String key) 094 { 095 keyBuffer = new StringBuilder(key); 096 removeTrailingDelimiter(); 097 } 098 099 /** 100 * Appends the name of a property to this key. If necessary, a 101 * property delimiter will be added. 102 * 103 * @param property the name of the property to be added 104 * @return a reference to this object 105 */ 106 public ConfigurationKey append(String property) 107 { 108 if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property)) 109 { 110 keyBuffer.append(PROPERTY_DELIMITER); 111 } 112 113 keyBuffer.append(property); 114 removeTrailingDelimiter(); 115 return this; 116 } 117 118 /** 119 * Appends an index to this configuration key. 120 * 121 * @param index the index to be appended 122 * @return a reference to this object 123 */ 124 public ConfigurationKey appendIndex(int index) 125 { 126 keyBuffer.append(INDEX_START).append(index); 127 keyBuffer.append(INDEX_END); 128 return this; 129 } 130 131 /** 132 * Appends an attribute to this configuration key. 133 * 134 * @param attr the name of the attribute to be appended 135 * @return a reference to this object 136 */ 137 public ConfigurationKey appendAttribute(String attr) 138 { 139 keyBuffer.append(constructAttributeKey(attr)); 140 return this; 141 } 142 143 /** 144 * Checks if this key is an attribute key. 145 * 146 * @return a flag if this key is an attribute key 147 */ 148 public boolean isAttributeKey() 149 { 150 return isAttributeKey(keyBuffer.toString()); 151 } 152 153 /** 154 * Checks if the passed in key is an attribute key. Such attribute keys 155 * start and end with certain marker strings. In some cases they must be 156 * treated slightly different. 157 * 158 * @param key the key (part) to be checked 159 * @return a flag if this key is an attribute key 160 */ 161 public static boolean isAttributeKey(String key) 162 { 163 return key != null 164 && key.startsWith(ATTRIBUTE_START) 165 && key.endsWith(ATTRIBUTE_END); 166 } 167 168 /** 169 * Decorates the given key so that it represents an attribute. Adds 170 * special start and end markers. 171 * 172 * @param key the key to be decorated 173 * @return the decorated attribute key 174 */ 175 public static String constructAttributeKey(String key) 176 { 177 StringBuilder buf = new StringBuilder(); 178 buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END); 179 return buf.toString(); 180 } 181 182 /** 183 * Extracts the name of the attribute from the given attribute key. 184 * This method removes the attribute markers - if any - from the 185 * specified key. 186 * 187 * @param key the attribute key 188 * @return the name of the corresponding attribute 189 */ 190 public static String attributeName(String key) 191 { 192 return isAttributeKey(key) ? removeAttributeMarkers(key) : key; 193 } 194 195 /** 196 * Helper method for removing attribute markers from a key. 197 * 198 * @param key the key 199 * @return the key with removed attribute markers 200 */ 201 static String removeAttributeMarkers(String key) 202 { 203 return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length()); 204 } 205 206 /** 207 * Helper method that checks if the actual buffer ends with a property 208 * delimiter. 209 * 210 * @return a flag if there is a trailing delimiter 211 */ 212 private boolean hasDelimiter() 213 { 214 int count = 0; 215 for (int idx = keyBuffer.length() - 1; idx >= 0 216 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--) 217 { 218 count++; 219 } 220 return count % 2 != 0; 221 } 222 223 /** 224 * Removes a trailing delimiter if there is any. 225 */ 226 private void removeTrailingDelimiter() 227 { 228 while (hasDelimiter()) 229 { 230 keyBuffer.deleteCharAt(keyBuffer.length() - 1); 231 } 232 } 233 234 /** 235 * Returns a string representation of this object. This is the 236 * configuration key as a plain string. 237 * 238 * @return a string for this object 239 */ 240 @Override 241 public String toString() 242 { 243 return keyBuffer.toString(); 244 } 245 246 /** 247 * Returns an iterator for iterating over the single components of 248 * this configuration key. 249 * 250 * @return an iterator for this key 251 */ 252 public KeyIterator iterator() 253 { 254 return new KeyIterator(); 255 } 256 257 /** 258 * Returns the actual length of this configuration key. 259 * 260 * @return the length of this key 261 */ 262 public int length() 263 { 264 return keyBuffer.length(); 265 } 266 267 /** 268 * Sets the new length of this configuration key. With this method it is 269 * possible to truncate the key, e.g. to return to a state prior calling 270 * some {@code append()} methods. The semantic is the same as 271 * the {@code setLength()} method of {@code StringBuilder}. 272 * 273 * @param len the new length of the key 274 */ 275 public void setLength(int len) 276 { 277 keyBuffer.setLength(len); 278 } 279 280 /** 281 * Checks if two {@code ConfigurationKey} objects are equal. The 282 * method can be called with strings or other objects, too. 283 * 284 * @param c the object to compare 285 * @return a flag if both objects are equal 286 */ 287 @Override 288 public boolean equals(Object c) 289 { 290 if (c == null) 291 { 292 return false; 293 } 294 295 return keyBuffer.toString().equals(c.toString()); 296 } 297 298 /** 299 * Returns the hash code for this object. 300 * 301 * @return the hash code 302 */ 303 @Override 304 public int hashCode() 305 { 306 return String.valueOf(keyBuffer).hashCode(); 307 } 308 309 /** 310 * Returns a configuration key object that is initialized with the part 311 * of the key that is common to this key and the passed in key. 312 * 313 * @param other the other key 314 * @return a key object with the common key part 315 */ 316 public ConfigurationKey commonKey(ConfigurationKey other) 317 { 318 if (other == null) 319 { 320 throw new IllegalArgumentException("Other key must no be null!"); 321 } 322 323 ConfigurationKey result = new ConfigurationKey(); 324 KeyIterator it1 = iterator(); 325 KeyIterator it2 = other.iterator(); 326 327 while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2)) 328 { 329 if (it1.isAttribute()) 330 { 331 result.appendAttribute(it1.currentKey()); 332 } 333 else 334 { 335 result.append(it1.currentKey()); 336 if (it1.hasIndex) 337 { 338 result.appendIndex(it1.getIndex()); 339 } 340 } 341 } 342 343 return result; 344 } 345 346 /** 347 * Returns the "difference key" to a given key. This value 348 * is the part of the passed in key that differs from this key. There is 349 * the following relation: 350 * {@code other = key.commonKey(other) + key.differenceKey(other)} 351 * for an arbitrary configuration key {@code key}. 352 * 353 * @param other the key for which the difference is to be calculated 354 * @return the difference key 355 */ 356 public ConfigurationKey differenceKey(ConfigurationKey other) 357 { 358 ConfigurationKey common = commonKey(other); 359 ConfigurationKey result = new ConfigurationKey(); 360 361 if (common.length() < other.length()) 362 { 363 String k = other.toString().substring(common.length()); 364 // skip trailing delimiters 365 int i = 0; 366 while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER) 367 { 368 i++; 369 } 370 371 if (i < k.length()) 372 { 373 result.append(k.substring(i)); 374 } 375 } 376 377 return result; 378 } 379 380 /** 381 * Helper method for comparing two key parts. 382 * 383 * @param it1 the iterator with the first part 384 * @param it2 the iterator with the second part 385 * @return a flag if both parts are equal 386 */ 387 private static boolean partsEqual(KeyIterator it1, KeyIterator it2) 388 { 389 return it1.nextKey().equals(it2.nextKey()) 390 && it1.getIndex() == it2.getIndex() 391 && it1.isAttribute() == it2.isAttribute(); 392 } 393 394 /** 395 * A specialized iterator class for tokenizing a configuration key. 396 * This class implements the normal iterator interface. In addition it 397 * provides some specific methods for configuration keys. 398 */ 399 public class KeyIterator implements Iterator<Object>, Cloneable 400 { 401 /** Stores the current key name.*/ 402 private String current; 403 404 /** Stores the start index of the actual token.*/ 405 private int startIndex; 406 407 /** Stores the end index of the actual token.*/ 408 private int endIndex; 409 410 /** Stores the index of the actual property if there is one.*/ 411 private int indexValue; 412 413 /** Stores a flag if the actual property has an index.*/ 414 private boolean hasIndex; 415 416 /** Stores a flag if the actual property is an attribute.*/ 417 private boolean attribute; 418 419 /** 420 * Helper method for determining the next indices. 421 * 422 * @return the next key part 423 */ 424 private String findNextIndices() 425 { 426 startIndex = endIndex; 427 // skip empty names 428 while (startIndex < keyBuffer.length() 429 && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER) 430 { 431 startIndex++; 432 } 433 434 // Key ends with a delimiter? 435 if (startIndex >= keyBuffer.length()) 436 { 437 endIndex = keyBuffer.length(); 438 startIndex = endIndex - 1; 439 return keyBuffer.substring(startIndex, endIndex); 440 } 441 else 442 { 443 return nextKeyPart(); 444 } 445 } 446 447 /** 448 * Helper method for extracting the next key part. Takes escaping of 449 * delimiter characters into account. 450 * 451 * @return the next key part 452 */ 453 private String nextKeyPart() 454 { 455 StringBuilder key = new StringBuilder(INITIAL_SIZE); 456 int idx = startIndex; 457 int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START, 458 startIndex); 459 if (endIdx < 0 || endIdx == startIndex) 460 { 461 endIdx = keyBuffer.length(); 462 } 463 boolean found = false; 464 465 while (!found && idx < endIdx) 466 { 467 char c = keyBuffer.charAt(idx); 468 if (c == PROPERTY_DELIMITER) 469 { 470 // a duplicated delimiter means escaping 471 if (idx == endIdx - 1 472 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER) 473 { 474 found = true; 475 } 476 else 477 { 478 idx++; 479 } 480 } 481 if (!found) 482 { 483 key.append(c); 484 idx++; 485 } 486 } 487 488 endIndex = idx; 489 return key.toString(); 490 } 491 492 /** 493 * Returns the next key part of this configuration key. This is a short 494 * form of {@code nextKey(false)}. 495 * 496 * @return the next key part 497 */ 498 public String nextKey() 499 { 500 return nextKey(false); 501 } 502 503 /** 504 * Returns the next key part of this configuration key. The boolean 505 * parameter indicates wheter a decorated key should be returned. This 506 * affects only attribute keys: if the parameter is <b>false</b>, the 507 * attribute markers are stripped from the key; if it is <b>true</b>, 508 * they remain. 509 * 510 * @param decorated a flag if the decorated key is to be returned 511 * @return the next key part 512 */ 513 public String nextKey(boolean decorated) 514 { 515 if (!hasNext()) 516 { 517 throw new NoSuchElementException("No more key parts!"); 518 } 519 520 hasIndex = false; 521 indexValue = -1; 522 String key = findNextIndices(); 523 524 current = key; 525 hasIndex = checkIndex(key); 526 attribute = checkAttribute(current); 527 528 return currentKey(decorated); 529 } 530 531 /** 532 * Helper method for checking if the passed key is an attribute. 533 * If this is the case, the internal fields will be set. 534 * 535 * @param key the key to be checked 536 * @return a flag if the key is an attribute 537 */ 538 private boolean checkAttribute(String key) 539 { 540 if (isAttributeKey(key)) 541 { 542 current = removeAttributeMarkers(key); 543 return true; 544 } 545 else 546 { 547 return false; 548 } 549 } 550 551 /** 552 * Helper method for checking if the passed key contains an index. 553 * If this is the case, internal fields will be set. 554 * 555 * @param key the key to be checked 556 * @return a flag if an index is defined 557 */ 558 private boolean checkIndex(String key) 559 { 560 boolean result = false; 561 562 int idx = key.lastIndexOf(INDEX_START); 563 if (idx > 0) 564 { 565 int endidx = key.indexOf(INDEX_END, idx); 566 567 if (endidx > idx + 1) 568 { 569 indexValue = Integer.parseInt(key.substring(idx + 1, endidx)); 570 current = key.substring(0, idx); 571 result = true; 572 } 573 } 574 575 return result; 576 } 577 578 /** 579 * Checks if there is a next element. 580 * 581 * @return a flag if there is a next element 582 */ 583 public boolean hasNext() 584 { 585 return endIndex < keyBuffer.length(); 586 } 587 588 /** 589 * Returns the next object in the iteration. 590 * 591 * @return the next object 592 */ 593 public Object next() 594 { 595 return nextKey(); 596 } 597 598 /** 599 * Removes the current object in the iteration. This method is not 600 * supported by this iterator type, so an exception is thrown. 601 */ 602 public void remove() 603 { 604 throw new UnsupportedOperationException("Remove not supported!"); 605 } 606 607 /** 608 * Returns the current key of the iteration (without skipping to the 609 * next element). This is the same key the previous {@code next()} 610 * call had returned. (Short form of {@code currentKey(false)}. 611 * 612 * @return the current key 613 */ 614 public String currentKey() 615 { 616 return currentKey(false); 617 } 618 619 /** 620 * Returns the current key of the iteration (without skipping to the 621 * next element). The boolean parameter indicates wheter a decorated 622 * key should be returned. This affects only attribute keys: if the 623 * parameter is <b>false</b>, the attribute markers are stripped from 624 * the key; if it is <b>true</b>, they remain. 625 * 626 * @param decorated a flag if the decorated key is to be returned 627 * @return the current key 628 */ 629 public String currentKey(boolean decorated) 630 { 631 return (decorated && isAttribute()) ? constructAttributeKey(current) : current; 632 } 633 634 /** 635 * Returns a flag if the current key is an attribute. This method can 636 * be called after {@code next()}. 637 * 638 * @return a flag if the current key is an attribute 639 */ 640 public boolean isAttribute() 641 { 642 return attribute; 643 } 644 645 /** 646 * Returns the index value of the current key. If the current key does 647 * not have an index, return value is -1. This method can be called 648 * after {@code next()}. 649 * 650 * @return the index value of the current key 651 */ 652 public int getIndex() 653 { 654 return indexValue; 655 } 656 657 /** 658 * Returns a flag if the current key has an associated index. 659 * This method can be called after {@code next()}. 660 * 661 * @return a flag if the current key has an index 662 */ 663 public boolean hasIndex() 664 { 665 return hasIndex; 666 } 667 668 /** 669 * Creates a clone of this object. 670 * 671 * @return a clone of this object 672 */ 673 @Override 674 public Object clone() 675 { 676 try 677 { 678 return super.clone(); 679 } 680 catch (CloneNotSupportedException cex) 681 { 682 // should not happen 683 return null; 684 } 685 } 686 } 687 }