Coverage Report - org.apache.commons.configuration.ConfigurationKey
 
Classes in this File Line Coverage Branch Coverage Complexity
ConfigurationKey
98%
65/66
100%
15/15
1,833
ConfigurationKey$KeyIterator
92%
55/60
100%
13/13
1,833
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 package org.apache.commons.configuration;
 19  
 
 20  
 import java.io.Serializable;
 21  
 import java.util.Iterator;
 22  
 import java.util.NoSuchElementException;
 23  
 
 24  
 /**
 25  
  * <p>A simple class that supports creation of and iteration on complex
 26  
  * configuration keys.</p>
 27  
  *
 28  
  * <p>For key creation the class works similar to a StringBuffer: There are
 29  
  * several <code>appendXXXX()</code> methods with which single parts
 30  
  * of a key can be constructed. All these methods return a reference to the
 31  
  * actual object so they can be written in a chain. When using this methods
 32  
  * the exact syntax for keys need not be known.</p>
 33  
  *
 34  
  * <p>This class also defines a specialized iterator for configuration keys.
 35  
  * With such an iterator a key can be tokenized into its single parts. For
 36  
  * each part it can be checked whether it has an associated index.</p>
 37  
  *
 38  
  * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
 39  
  * @version $Id: ConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $
 40  
  */
 41  9120
 public class ConfigurationKey implements Serializable
 42  
 {
 43  
     /** Constant for a property delimiter.*/
 44  
     public static final char PROPERTY_DELIMITER = '.';
 45  
 
 46  
     /** Constant for an escaped delimiter. */
 47  3
     public static final String ESCAPED_DELIMITER =
 48  
         String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
 49  
 
 50  
     /** Constant for an attribute start marker.*/
 51  
     private static final String ATTRIBUTE_START = "[@";
 52  
 
 53  
     /** Constant for an attribute end marker.*/
 54  
     private static final String ATTRIBUTE_END = "]";
 55  
 
 56  
     /** Constant for an index start marker.*/
 57  
     private static final char INDEX_START = '(';
 58  
 
 59  
     /** Constant for an index end marker.*/
 60  
     private static final char INDEX_END = ')';
 61  
 
 62  
     /** Constant for the initial StringBuffer size.*/
 63  
     private static final int INITIAL_SIZE = 32;
 64  
 
 65  
     /**
 66  
      * The serial version ID.
 67  
      */
 68  
     private static final long serialVersionUID = -4299732083605277656L;
 69  
 
 70  
     /** Holds a buffer with the so far created key.*/
 71  
     private StringBuffer keyBuffer;
 72  
 
 73  
     /**
 74  
      * Creates a new, empty instance of <code>ConfigurationKey</code>.
 75  
      */
 76  
     public ConfigurationKey()
 77  183
     {
 78  183
         keyBuffer = new StringBuffer(INITIAL_SIZE);
 79  183
     }
 80  
 
 81  
     /**
 82  
      * Creates a new instance of <code>ConfigurationKey</code> and
 83  
      * initializes it with the given key.
 84  
      *
 85  
      * @param key the key as a string
 86  
      */
 87  
     public ConfigurationKey(String key)
 88  54
     {
 89  54
         keyBuffer = new StringBuffer(key);
 90  54
         removeTrailingDelimiter();
 91  54
     }
 92  
 
 93  
     /**
 94  
      * Appends the name of a property to this key. If necessary, a
 95  
      * property delimiter will be added.
 96  
      *
 97  
      * @param property the name of the property to be added
 98  
      * @return a reference to this object
 99  
      */
 100  
     public ConfigurationKey append(String property)
 101  
     {
 102  257
         if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
 103  
         {
 104  108
             keyBuffer.append(PROPERTY_DELIMITER);
 105  
         }
 106  
 
 107  257
         keyBuffer.append(property);
 108  257
         removeTrailingDelimiter();
 109  257
         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  21
         keyBuffer.append(INDEX_START).append(index);
 121  21
         keyBuffer.append(INDEX_END);
 122  21
         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  5
         keyBuffer.append(constructAttributeKey(attr));
 134  5
         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  0
         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  681
         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  8
         StringBuffer buf = new StringBuffer();
 172  8
         buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
 173  8
         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  2
         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  7
         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  424
         int count = 0;
 209  424
         for (int idx = keyBuffer.length() - 1; idx >= 0
 210  8
                 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
 211  
         {
 212  8
             count++;
 213  
         }
 214  424
         return count % 2 == 1;
 215  
     }
 216  
 
 217  
     /**
 218  
      * Removes a trailing delimiter if there is any.
 219  
      */
 220  
     private void removeTrailingDelimiter()
 221  
     {
 222  626
         while (hasDelimiter())
 223  
         {
 224  4
             keyBuffer.deleteCharAt(keyBuffer.length() - 1);
 225  
         }
 226  311
     }
 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  121
         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  277
         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  203
         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  1
         keyBuffer.setLength(len);
 271  1
     }
 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  11
         if (c == null)
 283  
         {
 284  1
             return false;
 285  
         }
 286  
 
 287  10
         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  2
         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  104
         if (other == null)
 310  
         {
 311  1
             throw new IllegalArgumentException("Other key must no be null!");
 312  
         }
 313  
 
 314  103
         ConfigurationKey result = new ConfigurationKey();
 315  103
         KeyIterator it1 = iterator();
 316  103
         KeyIterator it2 = other.iterator();
 317  
 
 318  350
         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
 319  
         {
 320  144
             if (it1.isAttribute())
 321  
             {
 322  2
                 result.appendAttribute(it1.currentKey());
 323  
             }
 324  
             else
 325  
             {
 326  142
                 result.append(it1.currentKey());
 327  142
                 if (it1.hasIndex)
 328  
                 {
 329  7
                     result.appendIndex(it1.getIndex());
 330  
                 }
 331  
             }
 332  
         }
 333  
 
 334  103
         return result;
 335  
     }
 336  
 
 337  
     /**
 338  
      * Returns the &quot;difference key&quot; 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  67
         ConfigurationKey common = commonKey(other);
 350  67
         ConfigurationKey result = new ConfigurationKey();
 351  
 
 352  67
         if (common.length() < other.length())
 353  
         {
 354  63
             String k = other.toString().substring(common.length());
 355  
             // skip trailing delimiters
 356  63
             int i = 0;
 357  170
             while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
 358  
             {
 359  44
                 i++;
 360  
             }
 361  
 
 362  63
             if (i < k.length())
 363  
             {
 364  63
                 result.append(k.substring(i));
 365  
             }
 366  
         }
 367  
 
 368  67
         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  232
         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  419
     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  567
             startIndex = endIndex;
 418  
             // skip empty names
 419  567
             while (startIndex < keyBuffer.length()
 420  883
                     && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
 421  
             {
 422  316
                 startIndex++;
 423  
             }
 424  
 
 425  
             // Key ends with a delimiter?
 426  567
             if (startIndex >= keyBuffer.length())
 427  
             {
 428  0
                 endIndex = keyBuffer.length();
 429  0
                 startIndex = endIndex - 1;
 430  0
                 return keyBuffer.substring(startIndex, endIndex);
 431  
             }
 432  
             else
 433  
             {
 434  567
                 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  567
             StringBuffer key = new StringBuffer(INITIAL_SIZE);
 447  567
             int idx = startIndex;
 448  567
             int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
 449  
                     startIndex);
 450  567
             if (endIdx < 0 || endIdx == startIndex)
 451  
             {
 452  532
                 endIdx = keyBuffer.length();
 453  
             }
 454  567
             boolean found = false;
 455  
 
 456  5254
             while (!found && idx < endIdx)
 457  
             {
 458  4120
                 char c = keyBuffer.charAt(idx);
 459  4120
                 if (c == PROPERTY_DELIMITER)
 460  
                 {
 461  
                     // a duplicated delimiter means escaping
 462  385
                     if (idx == endIdx - 1
 463  
                             || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
 464  
                     {
 465  382
                         found = true;
 466  
                     }
 467  
                     else
 468  
                     {
 469  3
                         idx++;
 470  
                     }
 471  
                 }
 472  4120
                 if (!found)
 473  
                 {
 474  3738
                     key.append(c);
 475  3738
                     idx++;
 476  
                 }
 477  
             }
 478  
 
 479  567
             endIndex = idx;
 480  567
             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  566
             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  568
             if (!hasNext())
 507  
             {
 508  1
                 throw new NoSuchElementException("No more key parts!");
 509  
             }
 510  
 
 511  567
             hasIndex = false;
 512  567
             indexValue = -1;
 513  567
             String key = findNextIndices();
 514  
 
 515  567
             current = key;
 516  567
             hasIndex = checkIndex(key);
 517  567
             attribute = checkAttribute(current);
 518  
 
 519  567
             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  567
             if (isAttributeKey(key))
 532  
             {
 533  6
                 current = removeAttributeMarkers(key);
 534  6
                 return true;
 535  
             }
 536  
             else
 537  
             {
 538  561
                 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  567
             boolean result = false;
 552  
 
 553  567
             int idx = key.lastIndexOf(INDEX_START);
 554  567
             if (idx > 0)
 555  
             {
 556  23
                 int endidx = key.indexOf(INDEX_END, idx);
 557  
 
 558  23
                 if (endidx > idx + 1)
 559  
                 {
 560  22
                     indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
 561  22
                     current = key.substring(0, idx);
 562  22
                     result = true;
 563  
                 }
 564  
             }
 565  
 
 566  567
             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  1183
             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  5
             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  1
             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  144
             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  775
             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  499
             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  303
             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  5
             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  0
                 return super.clone();
 669  
             }
 670  
             catch (CloneNotSupportedException cex)
 671  
             {
 672  
                 // should not happen
 673  0
                 return null;
 674  
             }
 675  
         }
 676  
     }
 677  
 }