Coverage Report - org.apache.commons.configuration.ConfigurationKey
 
Classes in this File Line Coverage Branch Coverage Complexity
ConfigurationKey
98%
65/66
88%
44/50
2,306
ConfigurationKey$KeyIterator
90%
55/61
91%
33/36
2,306
 
 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 StringBuilder: There are
 29  
  * several {@code appendXXXX()} 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
 39  
  * href="http://commons.apache.org/configuration/team-list.html">Commons
 40  
  * Configuration team</a>
 41  
  * @version $Id: org.apache.commons.configuration.ConfigurationKey.html 910708 2014-05-31 19:13:45Z oheger $
 42  
  * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
 43  
  * instead. It is associated with a {@code DefaultExpressionEngine} and thus
 44  
  * can produce correct keys even if key separators have been changed.
 45  
  */
 46  
 @Deprecated
 47  9120
 public class ConfigurationKey implements Serializable
 48  
 {
 49  
     /** Constant for a property delimiter.*/
 50  
     public static final char PROPERTY_DELIMITER = '.';
 51  
 
 52  
     /** Constant for an escaped delimiter. */
 53  1
     public static final String ESCAPED_DELIMITER =
 54  
         String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
 55  
 
 56  
     /** Constant for an attribute start marker.*/
 57  
     private static final String ATTRIBUTE_START = "[@";
 58  
 
 59  
     /** Constant for an attribute end marker.*/
 60  
     private static final String ATTRIBUTE_END = "]";
 61  
 
 62  
     /** Constant for an index start marker.*/
 63  
     private static final char INDEX_START = '(';
 64  
 
 65  
     /** Constant for an index end marker.*/
 66  
     private static final char INDEX_END = ')';
 67  
 
 68  
     /** Constant for the initial StringBuilder size.*/
 69  
     private static final int INITIAL_SIZE = 32;
 70  
 
 71  
     /**
 72  
      * The serial version ID.
 73  
      */
 74  
     private static final long serialVersionUID = -4299732083605277656L;
 75  
 
 76  
     /** Holds a buffer with the so far created key.*/
 77  
     private StringBuilder keyBuffer;
 78  
 
 79  
     /**
 80  
      * Creates a new, empty instance of {@code ConfigurationKey}.
 81  
      */
 82  
     public ConfigurationKey()
 83  178
     {
 84  178
         keyBuffer = new StringBuilder(INITIAL_SIZE);
 85  178
     }
 86  
 
 87  
     /**
 88  
      * Creates a new instance of {@code ConfigurationKey} and
 89  
      * initializes it with the given key.
 90  
      *
 91  
      * @param key the key as a string
 92  
      */
 93  
     public ConfigurationKey(String key)
 94  49
     {
 95  49
         keyBuffer = new StringBuilder(key);
 96  49
         removeTrailingDelimiter();
 97  49
     }
 98  
 
 99  
     /**
 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  257
         if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
 109  
         {
 110  113
             keyBuffer.append(PROPERTY_DELIMITER);
 111  
         }
 112  
 
 113  257
         keyBuffer.append(property);
 114  257
         removeTrailingDelimiter();
 115  257
         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  10
         keyBuffer.append(INDEX_START).append(index);
 127  10
         keyBuffer.append(INDEX_END);
 128  10
         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  4
         keyBuffer.append(constructAttributeKey(attr));
 140  4
         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  0
         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  686
         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  7
         StringBuilder buf = new StringBuilder();
 178  7
         buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
 179  7
         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  2
         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  7
         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  424
         int count = 0;
 215  424
         for (int idx = keyBuffer.length() - 1; idx >= 0
 216  440
                 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
 217  
         {
 218  8
             count++;
 219  
         }
 220  424
         return count % 2 != 0;
 221  
     }
 222  
 
 223  
     /**
 224  
      * Removes a trailing delimiter if there is any.
 225  
      */
 226  
     private void removeTrailingDelimiter()
 227  
     {
 228  310
         while (hasDelimiter())
 229  
         {
 230  4
             keyBuffer.deleteCharAt(keyBuffer.length() - 1);
 231  
         }
 232  306
     }
 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  165
         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  277
         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  231
         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  29
         keyBuffer.setLength(len);
 278  29
     }
 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  11
         if (c == null)
 291  
         {
 292  1
             return false;
 293  
         }
 294  
 
 295  10
         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  2
         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  104
         if (other == null)
 319  
         {
 320  1
             throw new IllegalArgumentException("Other key must no be null!");
 321  
         }
 322  
 
 323  103
         ConfigurationKey result = new ConfigurationKey();
 324  103
         KeyIterator it1 = iterator();
 325  103
         KeyIterator it2 = other.iterator();
 326  
 
 327  247
         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
 328  
         {
 329  144
             if (it1.isAttribute())
 330  
             {
 331  2
                 result.appendAttribute(it1.currentKey());
 332  
             }
 333  
             else
 334  
             {
 335  142
                 result.append(it1.currentKey());
 336  142
                 if (it1.hasIndex)
 337  
                 {
 338  7
                     result.appendIndex(it1.getIndex());
 339  
                 }
 340  
             }
 341  
         }
 342  
 
 343  103
         return result;
 344  
     }
 345  
 
 346  
     /**
 347  
      * Returns the &quot;difference key&quot; 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  67
         ConfigurationKey common = commonKey(other);
 359  67
         ConfigurationKey result = new ConfigurationKey();
 360  
 
 361  67
         if (common.length() < other.length())
 362  
         {
 363  63
             String k = other.toString().substring(common.length());
 364  
             // skip trailing delimiters
 365  63
             int i = 0;
 366  107
             while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
 367  
             {
 368  44
                 i++;
 369  
             }
 370  
 
 371  63
             if (i < k.length())
 372  
             {
 373  63
                 result.append(k.substring(i));
 374  
             }
 375  
         }
 376  
 
 377  67
         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  232
         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  419
     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  567
             startIndex = endIndex;
 427  
             // skip empty names
 428  883
             while (startIndex < keyBuffer.length()
 429  
                     && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
 430  
             {
 431  316
                 startIndex++;
 432  
             }
 433  
 
 434  
             // Key ends with a delimiter?
 435  567
             if (startIndex >= keyBuffer.length())
 436  
             {
 437  0
                 endIndex = keyBuffer.length();
 438  0
                 startIndex = endIndex - 1;
 439  0
                 return keyBuffer.substring(startIndex, endIndex);
 440  
             }
 441  
             else
 442  
             {
 443  567
                 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  567
             StringBuilder key = new StringBuilder(INITIAL_SIZE);
 456  567
             int idx = startIndex;
 457  567
             int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
 458  
                     startIndex);
 459  567
             if (endIdx < 0 || endIdx == startIndex)
 460  
             {
 461  532
                 endIdx = keyBuffer.length();
 462  
             }
 463  567
             boolean found = false;
 464  
 
 465  4687
             while (!found && idx < endIdx)
 466  
             {
 467  4120
                 char c = keyBuffer.charAt(idx);
 468  4120
                 if (c == PROPERTY_DELIMITER)
 469  
                 {
 470  
                     // a duplicated delimiter means escaping
 471  385
                     if (idx == endIdx - 1
 472  
                             || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
 473  
                     {
 474  382
                         found = true;
 475  
                     }
 476  
                     else
 477  
                     {
 478  3
                         idx++;
 479  
                     }
 480  
                 }
 481  4120
                 if (!found)
 482  
                 {
 483  3738
                     key.append(c);
 484  3738
                     idx++;
 485  
                 }
 486  4120
             }
 487  
 
 488  567
             endIndex = idx;
 489  567
             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  566
             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  568
             if (!hasNext())
 516  
             {
 517  1
                 throw new NoSuchElementException("No more key parts!");
 518  
             }
 519  
 
 520  567
             hasIndex = false;
 521  567
             indexValue = -1;
 522  567
             String key = findNextIndices();
 523  
 
 524  567
             current = key;
 525  567
             hasIndex = checkIndex(key);
 526  567
             attribute = checkAttribute(current);
 527  
 
 528  567
             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  567
             if (isAttributeKey(key))
 541  
             {
 542  6
                 current = removeAttributeMarkers(key);
 543  6
                 return true;
 544  
             }
 545  
             else
 546  
             {
 547  561
                 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  567
             boolean result = false;
 561  
 
 562  567
             int idx = key.lastIndexOf(INDEX_START);
 563  567
             if (idx > 0)
 564  
             {
 565  23
                 int endidx = key.indexOf(INDEX_END, idx);
 566  
 
 567  23
                 if (endidx > idx + 1)
 568  
                 {
 569  22
                     indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
 570  22
                     current = key.substring(0, idx);
 571  22
                     result = true;
 572  
                 }
 573  
             }
 574  
 
 575  567
             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  1183
             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  5
             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  1
             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  144
             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  775
             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  499
             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  303
             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  5
             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  0
                 return super.clone();
 679  
             }
 680  0
             catch (CloneNotSupportedException cex)
 681  
             {
 682  
                 // should not happen
 683  0
                 return null;
 684  
             }
 685  
         }
 686  
     }
 687  
 }