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