    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 StringBuilder: There are
    029     * several {@code appendXXXX()} 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
    039     * href="http://commons.apache.org/configuration/team-list.html">Commons
    040     * Configuration team</a>
    041     * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
    042     * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
    043     * instead. It is associated with a {@code DefaultExpressionEngine} and thus
    044     * can produce correct keys even if key separators have been changed.
    045     */
    046    @Deprecated
    047    public class ConfigurationKey implements Serializable
    048    {
    049        /** Constant for a property delimiter.*/
    050        public static final char PROPERTY_DELIMITER = '.';
    051
    052        /** Constant for an escaped delimiter. */
    053        public static final String ESCAPED_DELIMITER =
    054            String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
    055
    056        /** Constant for an attribute start marker.*/
    057        private static final String ATTRIBUTE_START = "[@";
    058
    059        /** Constant for an attribute end marker.*/
    060        private static final String ATTRIBUTE_END = "]";
    061
    062        /** Constant for an index start marker.*/
    063        private static final char INDEX_START = '(';
    064
    065        /** Constant for an index end marker.*/
    066        private static final char INDEX_END = ')';
    067
    068        /** Constant for the initial StringBuilder size.*/
    069        private static final int INITIAL_SIZE = 32;
    070
    071        /**
    072         * The serial version ID.
    073         */
    074        private static final long serialVersionUID = -4299732083605277656L;
    075
    076        /** Holds a buffer with the so far created key.*/
    077        private StringBuilder keyBuffer;
    078
    079        /**
    080         * Creates a new, empty instance of {@code ConfigurationKey}.
    081         */
    082        public ConfigurationKey()
    083        {
    084            keyBuffer = new StringBuilder(INITIAL_SIZE);
    085        }
    086
    087        /**
    088         * Creates a new instance of {@code ConfigurationKey} and
    089         * initializes it with the given key.
    090         *
    091         * @param key the key as a string
    092         */
    093        public ConfigurationKey(String key)
    094        {
    095            keyBuffer = new StringBuilder(key);
    096            removeTrailingDelimiter();
    097        }
    098
    099        /**
    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            if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
    109            {
    110                keyBuffer.append(PROPERTY_DELIMITER);
    111            }
    112
    113            keyBuffer.append(property);
    114            removeTrailingDelimiter();
    115            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            keyBuffer.append(INDEX_START).append(index);
    127            keyBuffer.append(INDEX_END);
    128            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            keyBuffer.append(constructAttributeKey(attr));
    140            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            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            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            StringBuilder buf = new StringBuilder();
    178            buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
    179            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            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            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            int count = 0;
    215            for (int idx = keyBuffer.length() - 1; idx >= 0
    216                    && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
    217            {
    218                count++;
    219            }
    220            return count % 2 != 0;
    221        }
    222
    223        /**
    224         * Removes a trailing delimiter if there is any.
    225         */
    226        private void removeTrailingDelimiter()
    227        {
    228            while (hasDelimiter())
    229            {
    230                keyBuffer.deleteCharAt(keyBuffer.length() - 1);
    231            }
    232        }
    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            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            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            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            keyBuffer.setLength(len);
    278        }
    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            if (c == null)
    291            {
    292                return false;
    293            }
    294
    295            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            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            if (other == null)
    319            {
    320                throw new IllegalArgumentException("Other key must no be null!");
    321            }
    322
    323            ConfigurationKey result = new ConfigurationKey();
    324            KeyIterator it1 = iterator();
    325            KeyIterator it2 = other.iterator();
    326
    327            while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
    328            {
    329                if (it1.isAttribute())
    330                {
    331                    result.appendAttribute(it1.currentKey());
    332                }
    333                else
    334                {
    335                    result.append(it1.currentKey());
    336                    if (it1.hasIndex)
    337                    {
    338                        result.appendIndex(it1.getIndex());
    339                    }
    340                }
    341            }
    342
    343            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            ConfigurationKey common = commonKey(other);
    359            ConfigurationKey result = new ConfigurationKey();
    360
    361            if (common.length() < other.length())
    362            {
    363                String k = other.toString().substring(common.length());
    364                // skip trailing delimiters
    365                int i = 0;
    366                while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
    367                {
    368                    i++;
    369                }
    370
    371                if (i < k.length())
    372                {
    373                    result.append(k.substring(i));
    374                }
    375            }
    376
    377            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            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        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                startIndex = endIndex;
    427                // skip empty names
    428                while (startIndex < keyBuffer.length()
    429                        && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
    430                {
    431                    startIndex++;
    432                }
    433
    434                // Key ends with a delimiter?
    435                if (startIndex >= keyBuffer.length())
    436                {
    437                    endIndex = keyBuffer.length();
    438                    startIndex = endIndex - 1;
    439                    return keyBuffer.substring(startIndex, endIndex);
    440                }
    441                else
    442                {
    443                    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                StringBuilder key = new StringBuilder(INITIAL_SIZE);
    456                int idx = startIndex;
    457                int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
    458                        startIndex);
    459                if (endIdx < 0 || endIdx == startIndex)
    460                {
    461                    endIdx = keyBuffer.length();
    462                }
    463                boolean found = false;
    464
    465                while (!found && idx < endIdx)
    466                {
    467                    char c = keyBuffer.charAt(idx);
    468                    if (c == PROPERTY_DELIMITER)
    469                    {
    470                        // a duplicated delimiter means escaping
    471                        if (idx == endIdx - 1
    472                                || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
    473                        {
    474                            found = true;
    475                        }
    476                        else
    477                        {
    478                            idx++;
    479                        }
    480                    }
    481                    if (!found)
    482                    {
    483                        key.append(c);
    484                        idx++;
    485                    }
    486                }
    487
    488                endIndex = idx;
    489                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                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                if (!hasNext())
    516                {
    517                    throw new NoSuchElementException("No more key parts!");
    518                }
    519
    520                hasIndex = false;
    521                indexValue = -1;
    522                String key = findNextIndices();
    523
    524                current = key;
    525                hasIndex = checkIndex(key);
    526                attribute = checkAttribute(current);
    527
    528                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                if (isAttributeKey(key))
    541                {
    542                    current = removeAttributeMarkers(key);
    543                    return true;
    544                }
    545                else
    546                {
    547                    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                boolean result = false;
    561
    562                int idx = key.lastIndexOf(INDEX_START);
    563                if (idx > 0)
    564                {
    565                    int endidx = key.indexOf(INDEX_END, idx);
    566
    567                    if (endidx > idx + 1)
    568                    {
    569                        indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
    570                        current = key.substring(0, idx);
    571                        result = true;
    572                    }
    573                }
    574
    575                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                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                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                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                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                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                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                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                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                    return super.clone();
    679                }
    680                catch (CloneNotSupportedException cex)
    681                {
    682                    // should not happen
    683                    return null;
    684                }
    685            }
    686        }
    687    }