Coverage report

  %line %branch
org.apache.commons.configuration.ConfigurationKey$KeyIterator
94% 
100% 

 1  
 /*
 2  
  * Copyright 2004-2005 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License")
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.apache.commons.configuration;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.util.Iterator;
 21  
 import java.util.NoSuchElementException;
 22  
 
 23  
 /**
 24  
  * <p>A simple class that supports creation of and iteration on complex
 25  
  * configuration keys.</p>
 26  
  *
 27  
  * <p>For key creation the class works similar to a StringBuffer: There are
 28  
  * several <code>appendXXXX()</code> methods with which single parts
 29  
  * of a key can be constructed. All these methods return a reference to the
 30  
  * actual object so they can be written in a chain. When using this methods
 31  
  * the exact syntax for keys need not be known.</p>
 32  
  *
 33  
  * <p>This class also defines a specialized iterator for configuration keys.
 34  
  * With such an iterator a key can be tokenized into its single parts. For
 35  
  * each part it can be checked whether it has an associated index.</p>
 36  
  *
 37  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
 38  
  * @version $Id: ConfigurationKey.java 295090 2005-10-05 19:36:15Z oheger $
 39  
  */
 40  
 public class ConfigurationKey implements Serializable
 41  
 {
 42  
     /** Constant for a property delimiter.*/
 43  
     public static final char PROPERTY_DELIMITER = '.';
 44  
 
 45  
     /** Constant for an escaped delimiter. */
 46  
     public static final String ESCAPED_DELIMITER =
 47  
         String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
 48  
 
 49  
     /** Constant for an attribute start marker.*/
 50  
     private static final String ATTRIBUTE_START = "[@";
 51  
 
 52  
     /** Constant for an attribute end marker.*/
 53  
     private static final String ATTRIBUTE_END = "]";
 54  
 
 55  
     /** Constant for an index start marker.*/
 56  
     private static final char INDEX_START = '(';
 57  
 
 58  
     /** Constant for an index end marker.*/
 59  
     private static final char INDEX_END = ')';
 60  
 
 61  
     /** Constant for the initial StringBuffer size.*/
 62  
     private static final int INITIAL_SIZE = 32;
 63  
 
 64  
     /** Holds a buffer with the so far created key.*/
 65  
     private StringBuffer keyBuffer;
 66  
 
 67  
     /**
 68  
      * Creates a new, empty instance of <code>ConfigurationKey</code>.
 69  
      */
 70  
     public ConfigurationKey()
 71  
     {
 72  
         keyBuffer = new StringBuffer(INITIAL_SIZE);
 73  
     }
 74  
 
 75  
     /**
 76  
      * Creates a new instance of <code>ConfigurationKey</code> and
 77  
      * initializes it with the given key.
 78  
      *
 79  
      * @param key the key as a string
 80  
      */
 81  
     public ConfigurationKey(String key)
 82  
     {
 83  
         keyBuffer = new StringBuffer(key);
 84  
         removeTrailingDelimiter();
 85  
     }
 86  
 
 87  
     /**
 88  
      * Appends the name of a property to this key. If necessary, a
 89  
      * property delimiter will be added.
 90  
      *
 91  
      * @param property the name of the property to be added
 92  
      * @return a reference to this object
 93  
      */
 94  
     public ConfigurationKey append(String property)
 95  
     {
 96  
         if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
 97  
         {
 98  
             keyBuffer.append(PROPERTY_DELIMITER);
 99  
         }
 100  
 
 101  
         keyBuffer.append(property);
 102  
         removeTrailingDelimiter();
 103  
         return this;
 104  
     }
 105  
 
 106  
     /**
 107  
      * Appends an index to this configuration key.
 108  
      *
 109  
      * @param index the index to be appended
 110  
      * @return a reference to this object
 111  
      */
 112  
     public ConfigurationKey appendIndex(int index)
 113  
     {
 114  
         keyBuffer.append(INDEX_START).append(index);
 115  
         keyBuffer.append(INDEX_END);
 116  
         return this;
 117  
     }
 118  
 
 119  
     /**
 120  
      * Appends an attribute to this configuration key.
 121  
      *
 122  
      * @param attr the name of the attribute to be appended
 123  
      * @return a reference to this object
 124  
      */
 125  
     public ConfigurationKey appendAttribute(String attr)
 126  
     {
 127  
         keyBuffer.append(constructAttributeKey(attr));
 128  
         return this;
 129  
     }
 130  
 
 131  
     /**
 132  
      * Checks if this key is an attribute key.
 133  
      *
 134  
      * @return a flag if this key is an attribute key
 135  
      */
 136  
     public boolean isAttributeKey()
 137  
     {
 138  
         return isAttributeKey(keyBuffer.toString());
 139  
     }
 140  
 
 141  
     /**
 142  
      * Checks if the passed in key is an attribute key. Such attribute keys
 143  
      * start and end with certain marker strings. In some cases they must be
 144  
      * treated slightly different.
 145  
      *
 146  
      * @param key the key (part) to be checked
 147  
      * @return a flag if this key is an attribute key
 148  
      */
 149  
     public static boolean isAttributeKey(String key)
 150  
     {
 151  
         return key != null
 152  
         && key.startsWith(ATTRIBUTE_START)
 153  
         && key.endsWith(ATTRIBUTE_END);
 154  
     }
 155  
 
 156  
     /**
 157  
      * Decorates the given key so that it represents an attribute. Adds
 158  
      * special start and end markers.
 159  
      *
 160  
      * @param key the key to be decorated
 161  
      * @return the decorated attribute key
 162  
      */
 163  
     public static String constructAttributeKey(String key)
 164  
     {
 165  
         StringBuffer buf = new StringBuffer();
 166  
         buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
 167  
         return buf.toString();
 168  
     }
 169  
 
 170  
     /**
 171  
      * Extracts the name of the attribute from the given attribute key.
 172  
      * This method removes the attribute markers - if any - from the
 173  
      * specified key.
 174  
      *
 175  
      * @param key the attribute key
 176  
      * @return the name of the corresponding attribute
 177  
      */
 178  
     public static String attributeName(String key)
 179  
     {
 180  
         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
 181  
     }
 182  
 
 183  
     /**
 184  
      * Helper method for removing attribute markers from a key.
 185  
      *
 186  
      * @param key the key
 187  
      * @return the key with removed attribute markers
 188  
      */
 189  
     static String removeAttributeMarkers(String key)
 190  
     {
 191  
         return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
 192  
     }
 193  
 
 194  
     /**
 195  
      * Helper method that checks if the actual buffer ends with a property
 196  
      * delimiter.
 197  
      *
 198  
      * @return a flag if there is a trailing delimiter
 199  
      */
 200  
     private boolean hasDelimiter()
 201  
     {
 202  
         int count = 0;
 203  
         for (int idx = keyBuffer.length() - 1; idx >= 0
 204  
                 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
 205  
         {
 206  
             count++;
 207  
         }
 208  
         return count % 2 == 1;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Removes a trailing delimiter if there is any.
 213  
      */
 214  
     private void removeTrailingDelimiter()
 215  
     {
 216  
         while (hasDelimiter())
 217  
         {
 218  
             keyBuffer.deleteCharAt(keyBuffer.length() - 1);
 219  
         }
 220  
     }
 221  
 
 222  
     /**
 223  
      * Returns a string representation of this object. This is the
 224  
      * configuration key as a plain string.
 225  
      *
 226  
      * @return a string for this object
 227  
      */
 228  
     public String toString()
 229  
     {
 230  
         return keyBuffer.toString();
 231  
     }
 232  
 
 233  
     /**
 234  
      * Returns an iterator for iterating over the single components of
 235  
      * this configuration key.
 236  
      *
 237  
      * @return an iterator for this key
 238  
      */
 239  
     public KeyIterator iterator()
 240  
     {
 241  
         return new KeyIterator();
 242  
     }
 243  
 
 244  
     /**
 245  
      * Returns the actual length of this configuration key.
 246  
      *
 247  
      * @return the length of this key
 248  
      */
 249  
     public int length()
 250  
     {
 251  
         return keyBuffer.length();
 252  
     }
 253  
 
 254  
     /**
 255  
      * Sets the new length of this configuration key. With this method it is
 256  
      * possible to truncate the key, e.g. to return to a state prior calling
 257  
      * some <code>append()</code> methods. The semantic is the same as
 258  
      * the <code>setLength()</code> method of <code>StringBuffer</code>.
 259  
      *
 260  
      * @param len the new length of the key
 261  
      */
 262  
     public void setLength(int len)
 263  
     {
 264  
         keyBuffer.setLength(len);
 265  
     }
 266  
 
 267  
     /**
 268  
      * Checks if two <code>ConfigurationKey</code> objects are equal. The
 269  
      * method can be called with strings or other objects, too.
 270  
      *
 271  
      * @param c the object to compare
 272  
      * @return a flag if both objects are equal
 273  
      */
 274  
     public boolean equals(Object c)
 275  
     {
 276  
         if (c == null)
 277  
         {
 278  
             return false;
 279  
         }
 280  
 
 281  
         return keyBuffer.toString().equals(c.toString());
 282  
     }
 283  
 
 284  
     /**
 285  
      * Returns the hash code for this object.
 286  
      *
 287  
      * @return the hash code
 288  
      */
 289  
     public int hashCode()
 290  
     {
 291  
         return String.valueOf(keyBuffer).hashCode();
 292  
     }
 293  
 
 294  
     /**
 295  
      * Returns a configuration key object that is initialized with the part
 296  
      * of the key that is common to this key and the passed in key.
 297  
      *
 298  
      * @param other the other key
 299  
      * @return a key object with the common key part
 300  
      */
 301  
     public ConfigurationKey commonKey(ConfigurationKey other)
 302  
     {
 303  
         if (other == null)
 304  
         {
 305  
             throw new IllegalArgumentException("Other key must no be null!");
 306  
         }
 307  
 
 308  
         ConfigurationKey result = new ConfigurationKey();
 309  
         KeyIterator it1 = iterator();
 310  
         KeyIterator it2 = other.iterator();
 311  
 
 312  
         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
 313  
         {
 314  
             if (it1.isAttribute())
 315  
             {
 316  
                 result.appendAttribute(it1.currentKey());
 317  
             }
 318  
             else
 319  
             {
 320  
                 result.append(it1.currentKey());
 321  
                 if (it1.hasIndex)
 322  
                 {
 323  
                     result.appendIndex(it1.getIndex());
 324  
                 }
 325  
             }
 326  
         }
 327  
 
 328  
         return result;
 329  
     }
 330  
 
 331  
     /**
 332  
      * Returns the &quot;difference key&quot; to a given key. This value
 333  
      * is the part of the passed in key that differs from this key. There is
 334  
      * the following relation:
 335  
      * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
 336  
      * for an arbitrary configuration key <code>key</code>.
 337  
      *
 338  
      * @param other the key for which the difference is to be calculated
 339  
      * @return the difference key
 340  
      */
 341  
     public ConfigurationKey differenceKey(ConfigurationKey other)
 342  
     {
 343  
         ConfigurationKey common = commonKey(other);
 344  
         ConfigurationKey result = new ConfigurationKey();
 345  
 
 346  
         if (common.length() < other.length())
 347  
         {
 348  
             String k = other.toString().substring(common.length());
 349  
             // skip trailing delimiters
 350  
             int i = 0;
 351  
             while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
 352  
             {
 353  
                 i++;
 354  
             }
 355  
 
 356  
             if (i < k.length())
 357  
             {
 358  
                 result.append(k.substring(i));
 359  
             }
 360  
         }
 361  
 
 362  
         return result;
 363  
     }
 364  
 
 365  
     /**
 366  
      * Helper method for comparing two key parts.
 367  
      *
 368  
      * @param it1 the iterator with the first part
 369  
      * @param it2 the iterator with the second part
 370  
      * @return a flag if both parts are equal
 371  
      */
 372  
     private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
 373  
     {
 374  
         return it1.nextKey().equals(it2.nextKey())
 375  
         && it1.getIndex() == it2.getIndex()
 376  
         && it1.isAttribute() == it2.isAttribute();
 377  
     }
 378  
 
 379  
     /**
 380  
      * A specialized iterator class for tokenizing a configuration key.
 381  
      * This class implements the normal iterator interface. In addition it
 382  
      * provides some specific methods for configuration keys.
 383  
      */
 384  7077
     public class KeyIterator implements Iterator, Cloneable
 385  
     {
 386  
         /** Stores the current key name.*/
 387  
         private String current;
 388  
 
 389  
         /** Stores the start index of the actual token.*/
 390  
         private int startIndex;
 391  
 
 392  
         /** Stores the end index of the actual token.*/
 393  
         private int endIndex;
 394  
 
 395  
         /** Stores the index of the actual property if there is one.*/
 396  
         private int indexValue;
 397  
 
 398  
         /** Stores a flag if the actual property has an index.*/
 399  
         private boolean hasIndex;
 400  
 
 401  
         /** Stores a flag if the actual property is an attribute.*/
 402  
         private boolean attribute;
 403  
 
 404  
         /**
 405  
          * Helper method for determining the next indices.
 406  
          *
 407  
          * @return the next key part
 408  
          */
 409  
         private String findNextIndices()
 410  
         {
 411  15519
             startIndex = endIndex;
 412  
             // skip empty names
 413  15519
             while (startIndex < keyBuffer.length()
 414  23649
                     && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
 415  
             {
 416  8130
                 startIndex++;
 417  
             }
 418  
 
 419  
             // Key ends with a delimiter?
 420  15519
             if (startIndex >= keyBuffer.length())
 421  
             {
 422  0
                 endIndex = keyBuffer.length();
 423  0
                 startIndex = endIndex - 1;
 424  0
                 return keyBuffer.substring(startIndex, endIndex);
 425  
             }
 426  
             else
 427  
             {
 428  15519
                 return nextKeyPart();
 429  
             }
 430  
         }
 431  
 
 432  
         /**
 433  
          * Helper method for extracting the next key part. Takes escaping of
 434  
          * delimiter characters into account.
 435  
          *
 436  
          * @return the next key part
 437  
          */
 438  
         private String nextKeyPart()
 439  
         {
 440  15519
             StringBuffer key = new StringBuffer(INITIAL_SIZE);
 441  15519
             int idx = startIndex;
 442  15519
             int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
 443  
                     startIndex);
 444  15519
             if (endIdx < 0 || endIdx == startIndex)
 445  
             {
 446  13956
                 endIdx = keyBuffer.length();
 447  
             }
 448  15519
             boolean found = false;
 449  
 
 450  127191
             while (!found && idx < endIdx)
 451  
             {
 452  96153
                 char c = keyBuffer.class="keyword">charAt(idx);
 453  96153
                 if (c == PROPERTY_DELIMITER)
 454  
                 {
 455  
                     // a duplicated delimiter means escaping
 456  7464
                     if (idx == endIdx - 1
 457  
                             || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
 458  
                     {
 459  7284
                         found = true;
 460  
                     }
 461  
                     else
 462  
                     {
 463  180
                         idx++;
 464  
                     }
 465  
                 }
 466  96153
                 if (!found)
 467  
                 {
 468  88869
                     key.append(c);
 469  88869
                     idx++;
 470  
                 }
 471  
             }
 472  
 
 473  15519
             endIndex = idx;
 474  15519
             return key.toString();
 475  
         }
 476  
 
 477  
         /**
 478  
          * Returns the next key part of this configuration key. This is a short
 479  
          * form of <code>nextKey(false)</code>.
 480  
          *
 481  
          * @return the next key part
 482  
          */
 483  
         public String nextKey()
 484  
         {
 485  4857
             return nextKey(false);
 486  
         }
 487  
 
 488  
         /**
 489  
          * Returns the next key part of this configuration key. The boolean
 490  
          * parameter indicates wheter a decorated key should be returned. This
 491  
          * affects only attribute keys: if the parameter is <b>false</b>, the
 492  
          * attribute markers are stripped from the key; if it is <b>true</b>,
 493  
          * they remain.
 494  
          *
 495  
          * @param decorated a flag if the decorated key is to be returned
 496  
          * @return the next key part
 497  
          */
 498  
         public String nextKey(boolean decorated)
 499  
         {
 500  15522
             if (!hasNext())
 501  
             {
 502  3
                 throw new NoSuchElementException("No more key parts!");
 503  
             }
 504  
 
 505  15519
             hasIndex = false;
 506  15519
             indexValue = -1;
 507  15519
             String key = findNextIndices();
 508  
 
 509  15519
             attribute = checkAttribute(key);
 510  15519
             if (!attribute)
 511  
             {
 512  14127
                 hasIndex = checkIndex(key);
 513  14127
                 if (!hasIndex)
 514  
                 {
 515  13440
                     current = key;
 516  
                 }
 517  
             }
 518  
 
 519  15519
             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  15519
             if (isAttributeKey(key))
 532  
             {
 533  1392
                 current = removeAttributeMarkers(key);
 534  1392
                 return true;
 535  
             }
 536  
             else
 537  
             {
 538  14127
                 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  14127
             boolean result = false;
 552  
 
 553  14127
             int idx = key.lastIndexOf(INDEX_START);
 554  14127
             if (idx > 0)
 555  
             {
 556  690
                 int endidx = key.indexOf(INDEX_END, idx);
 557  
 
 558  690
                 if (endidx > idx + 1)
 559  
                 {
 560  687
                     indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
 561  687
                     current = key.substring(0, idx);
 562  687
                     result = true;
 563  
                 }
 564  
             }
 565  
 
 566  14127
             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  37881
             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  114
             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  3
             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  915
             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  17655
             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  14238
             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  3237
             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  10476
             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  12669
                 return super.clone();
 669  
             }
 670  
             catch (CloneNotSupportedException cex)
 671  
             {
 672  
                 // should not happen
 673  0
                 return null;
 674  
             }
 675  
         }
 676  
     }
 677  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.