    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.configuration;
    019
    020    import java.io.Serializable;
    021    import java.util.Iterator;
    022    import java.util.NoSuchElementException;
    023
    024    /**
    025     * <p>A simple class that supports creation of and iteration on complex
    026     * configuration keys.</p>
    027     *
    028     * <p>For key creation the class works similar to a StringBuffer: There are
    029     * several <code>appendXXXX()</code> methods with which single parts
    030     * of a key can be constructed. All these methods return a reference to the
    031     * actual object so they can be written in a chain. When using this methods
    032     * the exact syntax for keys need not be known.</p>
    033     *
    034     * <p>This class also defines a specialized iterator for configuration keys.
    035     * With such an iterator a key can be tokenized into its single parts. For
    036     * each part it can be checked whether it has an associated index.</p>
    037     *
    038     * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
    039     * @version $Id: ConfigurationKey.java 726809 2008-12-15 21:29:56Z oheger $
    040     */
    041    public class ConfigurationKey implements Serializable
    042    {
    043        /** Constant for a property delimiter.*/
    044        public static final char PROPERTY_DELIMITER = '.';
    045
    046        /** Constant for an escaped delimiter. */
    047        public static final String ESCAPED_DELIMITER =
    048            String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
    049
    050        /** Constant for an attribute start marker.*/
    051        private static final String ATTRIBUTE_START = "[@";
    052
    053        /** Constant for an attribute end marker.*/
    054        private static final String ATTRIBUTE_END = "]";
    055
    056        /** Constant for an index start marker.*/
    057        private static final char INDEX_START = '(';
    058
    059        /** Constant for an index end marker.*/
    060        private static final char INDEX_END = ')';
    061
    062        /** Constant for the initial StringBuffer size.*/
    063        private static final int INITIAL_SIZE = 32;
    064
    065        /**
    066         * The serial version ID.
    067         */
    068        private static final long serialVersionUID = -4299732083605277656L;
    069
    070        /** Holds a buffer with the so far created key.*/
    071        private StringBuffer keyBuffer;
    072
    073        /**
    074         * Creates a new, empty instance of <code>ConfigurationKey</code>.
    075         */
    076        public ConfigurationKey()
    077        {
    078            keyBuffer = new StringBuffer(INITIAL_SIZE);
    079        }
    080
    081        /**
    082         * Creates a new instance of <code>ConfigurationKey</code> and
    083         * initializes it with the given key.
    084         *
    085         * @param key the key as a string
    086         */
    087        public ConfigurationKey(String key)
    088        {
    089            keyBuffer = new StringBuffer(key);
    090            removeTrailingDelimiter();
    091        }
    092
    093        /**
    094         * Appends the name of a property to this key. If necessary, a
    095         * property delimiter will be added.
    096         *
    097         * @param property the name of the property to be added
    098         * @return a reference to this object
    099         */
    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 != 0;
    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    }