View Javadoc

    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  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      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      {
    78          keyBuffer = new StringBuffer(INITIAL_SIZE);
    79      }
    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      {
    89          keyBuffer = new StringBuffer(key);
    90          removeTrailingDelimiter();
    91      }
    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         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 == 1;
    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 &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         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 }