View Javadoc

    1   /*
    2    * Copyright 2004 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,v 1.5 2004/08/14 11:23:14 epugh Exp $
    39   */
    40  public class ConfigurationKey implements Serializable
    41  {
    42      /*** Constant for an attribute start marker.*/
    43      private static final String ATTRIBUTE_START = "[@";
    44
    45      /*** Constant for an attribute end marker.*/
    46      private static final String ATTRIBUTE_END = "]";
    47
    48      /*** Constant for a property delimiter.*/
    49      private static final char PROPERTY_DELIMITER = '.';
    50
    51      /*** Constant for an index start marker.*/
    52      private static final char INDEX_START = '(';
    53
    54      /*** Constant for an index end marker.*/
    55      private static final char INDEX_END = ')';
    56
    57      /*** Constant for the initial StringBuffer size.*/
    58      private static final int INITIAL_SIZE = 32;
    59
    60      /*** Holds a buffer with the so far created key.*/
    61      private StringBuffer keyBuffer;
    62
    63      /***
    64       * Creates a new, empty instance of <code>ConfigurationKey</code>.
    65       */
    66      public ConfigurationKey()
    67      {
    68          keyBuffer = new StringBuffer(INITIAL_SIZE);
    69      }
    70
    71      /***
    72       * Creates a new instance of <code>ConfigurationKey</code> and
    73       * initializes it with the given key.
    74       *
    75       * @param key the key as a string
    76       */
    77      public ConfigurationKey(String key)
    78      {
    79          keyBuffer = new StringBuffer(key);
    80          removeTrailingDelimiter();
    81      }
    82
    83      /***
    84       * Appends the name of a property to this key. If necessary, a
    85       * property delimiter will be added.
    86       *
    87       * @param property the name of the property to be added
    88       * @return a reference to this object
    89       */
    90      public ConfigurationKey append(String property)
    91      {
    92          if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
    93          {
    94              keyBuffer.append(PROPERTY_DELIMITER);
    95          }
    96
    97          keyBuffer.append(property);
    98          removeTrailingDelimiter();
    99          return this;
    100     }
    101
    102     /***
    103      * Appends an index to this configuration key.
    104      *
    105      * @param index the index to be appended
    106      * @return a reference to this object
    107      */
    108     public ConfigurationKey appendIndex(int index)
    109     {
    110         keyBuffer.append(INDEX_START).append(index);
    111         keyBuffer.append(INDEX_END);
    112         return this;
    113     }
    114
    115     /***
    116      * Appends an attribute to this configuration key.
    117      *
    118      * @param attr the name of the attribute to be appended
    119      * @return a reference to this object
    120      */
    121     public ConfigurationKey appendAttribute(String attr)
    122     {
    123         keyBuffer.append(constructAttributeKey(attr));
    124         return this;
    125     }
    126
    127     /***
    128      * Checks if the passed in key is an attribute key. Such attribute keys
    129      * start and end with certain marker strings. In some cases they must be
    130      * treated slightly different.
    131      *
    132      * @param key the key (part) to be checked
    133      * @return a flag if this key is an attribute key
    134      */
    135     public static boolean isAttributeKey(String key)
    136     {
    137         return key != null
    138         && key.startsWith(ATTRIBUTE_START)
    139         && key.endsWith(ATTRIBUTE_END);
    140     }
    141
    142     /***
    143      * Decorates the given key so that it represents an attribute. Adds
    144      * special start and end markers.
    145      *
    146      * @param key the key to be decorated
    147      * @return the decorated attribute key
    148      */
    149     public static String constructAttributeKey(String key)
    150     {
    151         StringBuffer buf = new StringBuffer();
    152         buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
    153         return buf.toString();
    154     }
    155
    156     /***
    157      * Extracts the name of the attribute from the given attribute key.
    158      * This method removes the attribute markers - if any - from the
    159      * specified key.
    160      *
    161      * @param key the attribute key
    162      * @return the name of the corresponding attribute
    163      */
    164     public static String attributeName(String key)
    165     {
    166         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
    167     }
    168
    169     /***
    170      * Helper method for removing attribute markers from a key.
    171      *
    172      * @param key the key
    173      * @return the key with removed attribute markers
    174      */
    175     private static String removeAttributeMarkers(String key)
    176     {
    177         return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
    178     }
    179
    180     /***
    181      * Helper method that checks if the actual buffer ends with a property
    182      * delimiter.
    183      *
    184      * @return a flag if there is a trailing delimiter
    185      */
    186     private boolean hasDelimiter()
    187     {
    188         return keyBuffer.length() > 0
    189         && keyBuffer.charAt(keyBuffer.length() - 1) == PROPERTY_DELIMITER;
    190     }
    191
    192     /***
    193      * Removes a trailing delimiter if there is any.
    194      */
    195     private void removeTrailingDelimiter()
    196     {
    197         while (hasDelimiter())
    198         {
    199             keyBuffer.deleteCharAt(keyBuffer.length() - 1);
    200         }
    201     }
    202
    203     /***
    204      * Returns a string representation of this object. This is the
    205      * configuration key as a plain string.
    206      *
    207      * @return a string for this object
    208      */
    209     public String toString()
    210     {
    211         return keyBuffer.toString();
    212     }
    213
    214     /***
    215      * Returns an iterator for iterating over the single components of
    216      * this configuration key.
    217      *
    218      * @return an iterator for this key
    219      */
    220     public KeyIterator iterator()
    221     {
    222         return new KeyIterator();
    223     }
    224
    225     /***
    226      * Returns the actual length of this configuration key.
    227      *
    228      * @return the length of this key
    229      */
    230     public int length()
    231     {
    232         return keyBuffer.length();
    233     }
    234
    235     /***
    236      * Sets the new length of this configuration key. With this method it is
    237      * possible to truncate the key, e.g. to return to a state prior calling
    238      * some <code>append()</code> methods. The semantic is the same as
    239      * the <code>setLength()</code> method of <code>StringBuffer</code>.
    240      *
    241      * @param len the new length of the key
    242      */
    243     public void setLength(int len)
    244     {
    245         keyBuffer.setLength(len);
    246     }
    247
    248     /***
    249      * Checks if two <code>ConfigurationKey</code> objects are equal. The
    250      * method can be called with strings or other objects, too.
    251      *
    252      * @param c the object to compare
    253      * @return a flag if both objects are equal
    254      */
    255     public boolean equals(Object c)
    256     {
    257         if (c == null)
    258         {
    259             return false;
    260         }
    261
    262         return keyBuffer.toString().equals(c.toString());
    263     }
    264
    265     /***
    266      * Returns the hash code for this object.
    267      *
    268      * @return the hash code
    269      */
    270     public int hashCode()
    271     {
    272         return keyBuffer.hashCode();
    273     }
    274
    275     /***
    276      * Returns a configuration key object that is initialized with the part
    277      * of the key that is common to this key and the passed in key.
    278      *
    279      * @param other the other key
    280      * @return a key object with the common key part
    281      */
    282     public ConfigurationKey commonKey(ConfigurationKey other)
    283     {
    284         if (other == null)
    285         {
    286             throw new IllegalArgumentException("Other key must no be null!");
    287         }
    288
    289         ConfigurationKey result = new ConfigurationKey();
    290         KeyIterator it1 = iterator();
    291         KeyIterator it2 = other.iterator();
    292
    293         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
    294         {
    295             if (it1.isAttribute())
    296             {
    297                 result.appendAttribute(it1.currentKey());
    298             }
    299             else
    300             {
    301                 result.append(it1.currentKey());
    302                 if (it1.hasIndex)
    303                 {
    304                     result.appendIndex(it1.getIndex());
    305                 }
    306             }
    307         }
    308
    309         return result;
    310     }
    311
    312     /***
    313      * Returns the &quot;difference key&quot; to a given key. This value
    314      * is the part of the passed in key that differs from this key. There is
    315      * the following relation:
    316      * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
    317      * for an arbitrary configuration key <code>key</code>.
    318      *
    319      * @param other the key for which the difference is to be calculated
    320      * @return the difference key
    321      */
    322     public ConfigurationKey differenceKey(ConfigurationKey other)
    323     {
    324         ConfigurationKey common = commonKey(other);
    325         ConfigurationKey result = new ConfigurationKey();
    326
    327         if (common.length() < other.length())
    328         {
    329             String k = other.toString().substring(common.length());
    330             // skip trailing delimiters
    331             int i = 0;
    332             while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
    333             {
    334                 i++;
    335             }
    336
    337             if (i < k.length())
    338             {
    339                 result.append(k.substring(i));
    340             }
    341         }
    342
    343         return result;
    344     }
    345
    346     /***
    347      * Helper method for comparing two key parts.
    348      *
    349      * @param it1 the iterator with the first part
    350      * @param it2 the iterator with the second part
    351      * @return a flag if both parts are equal
    352      */
    353     private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
    354     {
    355         return it1.nextKey().equals(it2.nextKey())
    356         && it1.getIndex() == it2.getIndex()
    357         && it1.isAttribute() == it2.isAttribute();
    358     }
    359
    360     /***
    361      * A specialized iterator class for tokenizing a configuration key.
    362      * This class implements the normal iterator interface. In addition it
    363      * provides some specific methods for configuration keys.
    364      */
    365     public class KeyIterator implements Iterator, Cloneable
    366     {
    367         /*** Stores the current key name.*/
    368         private String current;
    369
    370         /*** Stores the start index of the actual token.*/
    371         private int startIndex;
    372
    373         /*** Stores the end index of the actual token.*/
    374         private int endIndex;
    375
    376         /*** Stores the index of the actual property if there is one.*/
    377         private int indexValue;
    378
    379         /*** Stores a flag if the actual property has an index.*/
    380         private boolean hasIndex;
    381
    382         /*** Stores a flag if the actual property is an attribute.*/
    383         private boolean attribute;
    384
    385         /***
    386          * Helper method for determining the next indices.
    387          */
    388         private void findNextIndices()
    389         {
    390             startIndex = endIndex;
    391             // skip empty names
    392             while (startIndex < keyBuffer.length()
    393             && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
    394             {
    395                 startIndex++;
    396             }
    397
    398             // Key ends with a delimiter?
    399             if (startIndex >= keyBuffer.length())
    400             {
    401                 endIndex = keyBuffer.length();
    402                 startIndex = endIndex - 1;
    403             }
    404             else
    405             {
    406                 String s = keyBuffer.toString();    // for compatibility
    407                 endIndex = s.indexOf(PROPERTY_DELIMITER, startIndex);
    408                 if (endIndex < 0)
    409                 {
    410                     endIndex = s.indexOf(ATTRIBUTE_START, startIndex);
    411                     if (endIndex < 0 || endIndex == startIndex)
    412                     {
    413                         endIndex = keyBuffer.length();
    414                     }
    415                 }
    416             }
    417         }
    418
    419         /***
    420          * Returns the next key part of this configuration key. This is a short
    421          * form of <code>nextKey(false)</code>.
    422          *
    423          * @return the next key part
    424          */
    425         public String nextKey()
    426         {
    427             return nextKey(false);
    428         }
    429
    430         /***
    431          * Returns the next key part of this configuration key. The boolean
    432          * parameter indicates wheter a decorated key should be returned. This
    433          * affects only attribute keys: if the parameter is <b>false</b>, the
    434          * attribute markers are stripped from the key; if it is <b>true</b>,
    435          * they remain.
    436          *
    437          * @param decorated a flag if the decorated key is to be returned
    438          * @return the next key part
    439          */
    440         public String nextKey(boolean decorated)
    441         {
    442             if (!hasNext())
    443             {
    444                 throw new NoSuchElementException("No more key parts!");
    445             }
    446
    447             hasIndex = false;
    448             indexValue = -1;
    449             findNextIndices();
    450             String key = keyBuffer.substring(startIndex, endIndex);
    451
    452             attribute = checkAttribute(key);
    453             if (!attribute)
    454             {
    455                 hasIndex = checkIndex(key);
    456                 if (!hasIndex)
    457                 {
    458                     current = key;
    459                 }
    460             }
    461
    462             return currentKey(decorated);
    463         }
    464
    465         /***
    466          * Helper method for checking if the passed key is an attribute.
    467          * If this is the case, the internal fields will be set.
    468          *
    469          * @param key the key to be checked
    470          * @return a flag if the key is an attribute
    471          */
    472         private boolean checkAttribute(String key)
    473         {
    474             if (isAttributeKey(key))
    475             {
    476                 current = removeAttributeMarkers(key);
    477                 return true;
    478             }
    479             else
    480             {
    481                 return false;
    482             }
    483         }
    484
    485         /***
    486          * Helper method for checking if the passed key contains an index.
    487          * If this is the case, internal fields will be set.
    488          *
    489          * @param key the key to be checked
    490          * @return a flag if an index is defined
    491          */
    492         private boolean checkIndex(String key)
    493         {
    494             boolean result = false;
    495
    496             int idx = key.indexOf(INDEX_START);
    497             if (idx > 0)
    498             {
    499                 int endidx = key.indexOf(INDEX_END, idx);
    500
    501                 if (endidx > idx + 1)
    502                 {
    503                     indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
    504                     current = key.substring(0, idx);
    505                     result = true;
    506                 }
    507             }
    508
    509             return result;
    510         }
    511
    512         /***
    513          * Checks if there is a next element.
    514          *
    515          * @return a flag if there is a next element
    516          */
    517         public boolean hasNext()
    518         {
    519             return endIndex < keyBuffer.length();
    520         }
    521
    522         /***
    523          * Returns the next object in the iteration.
    524          *
    525          * @return the next object
    526          */
    527         public Object next()
    528         {
    529             return nextKey();
    530         }
    531
    532         /***
    533          * Removes the current object in the iteration. This method is not
    534          * supported by this iterator type, so an exception is thrown.
    535          */
    536         public void remove()
    537         {
    538             throw new UnsupportedOperationException("Remove not supported!");
    539         }
    540
    541         /***
    542          * Returns the current key of the iteration (without skipping to the
    543          * next element). This is the same key the previous <code>next()</code>
    544          * call had returned. (Short form of <code>currentKey(false)</code>.
    545          *
    546          * @return the current key
    547          */
    548         public String currentKey()
    549         {
    550             return currentKey(false);
    551         }
    552
    553         /***
    554          * Returns the current key of the iteration (without skipping to the
    555          * next element). The boolean parameter indicates wheter a decorated
    556          * key should be returned. This affects only attribute keys: if the
    557          * parameter is <b>false</b>, the attribute markers are stripped from
    558          * the key; if it is <b>true</b>, they remain.
    559          *
    560          * @param decorated a flag if the decorated key is to be returned
    561          * @return the current key
    562          */
    563         public String currentKey(boolean decorated)
    564         {
    565             return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
    566         }
    567
    568         /***
    569          * Returns a flag if the current key is an attribute. This method can
    570          * be called after <code>next()</code>.
    571          *
    572          * @return a flag if the current key is an attribute
    573          */
    574         public boolean isAttribute()
    575         {
    576             return attribute;
    577         }
    578
    579         /***
    580          * Returns the index value of the current key. If the current key does
    581          * not have an index, return value is -1. This method can be called
    582          * after <code>next()</code>.
    583          *
    584          * @return the index value of the current key
    585          */
    586         public int getIndex()
    587         {
    588             return indexValue;
    589         }
    590
    591         /***
    592          * Returns a flag if the current key has an associated index.
    593          * This method can be called after <code>next()</code>.
    594          *
    595          * @return a flag if the current key has an index
    596          */
    597         public boolean hasIndex()
    598         {
    599             return hasIndex;
    600         }
    601
    602         /***
    603          * Creates a clone of this object.
    604          *
    605          * @return a clone of this object
    606          */
    607         protected Object clone()
    608         {
    609             try
    610             {
    611                 return super.clone();
    612             }
    613             catch (CloneNotSupportedException cex)
    614             {
    615                 // should not happen
    616                 return null;
    617             }
    618         }
    619     }
    620 }