    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.util.Iterator;
    021
    022    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
    023
    024    /**
    025     * <p>A subset of another configuration. The new Configuration object contains
    026     * every key from the parent Configuration that starts with prefix. The prefix
    027     * is removed from the keys in the subset.</p>
    028     * <p>It is usually not necessary to use this class directly. Instead the
    029     * {@link Configuration#subset(String)} method should be used,
    030     * which will return a correctly initialized instance.</p>
    031     *
    032     * @author Emmanuel Bourg
    033     * @version $Id: SubsetConfiguration.java 1210202 2011-12-04 20:30:46Z oheger $
    034     */
    035    public class SubsetConfiguration extends AbstractConfiguration
    036    {
    037        /** The parent configuration. */
    038        protected Configuration parent;
    039
    040        /** The prefix used to select the properties. */
    041        protected String prefix;
    042
    043        /** The prefix delimiter */
    044        protected String delimiter;
    045
    046        /**
    047         * Create a subset of the specified configuration
    048         *
    049         * @param parent The parent configuration
    050         * @param prefix The prefix used to select the properties
    051         */
    052        public SubsetConfiguration(Configuration parent, String prefix)
    053        {
    054            this.parent = parent;
    055            this.prefix = prefix;
    056        }
    057
    058        /**
    059         * Create a subset of the specified configuration
    060         *
    061         * @param parent    The parent configuration
    062         * @param prefix    The prefix used to select the properties
    063         * @param delimiter The prefix delimiter
    064         */
    065        public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
    066        {
    067            this.parent = parent;
    068            this.prefix = prefix;
    069            this.delimiter = delimiter;
    070        }
    071
    072        /**
    073         * Return the key in the parent configuration associated to the specified
    074         * key in this subset.
    075         *
    076         * @param key The key in the subset.
    077         * @return the key as to be used by the parent
    078         */
    079        protected String getParentKey(String key)
    080        {
    081            if ("".equals(key) || key == null)
    082            {
    083                return prefix;
    084            }
    085            else
    086            {
    087                return delimiter == null ? prefix + key : prefix + delimiter + key;
    088            }
    089        }
    090
    091        /**
    092         * Return the key in the subset configuration associated to the specified
    093         * key in the parent configuration.
    094         *
    095         * @param key The key in the parent configuration.
    096         * @return the key in the context of this subset configuration
    097         */
    098        protected String getChildKey(String key)
    099        {
    100            if (!key.startsWith(prefix))
    101            {
    102                throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
    103            }
    104            else
    105            {
    106                String modifiedKey = null;
    107                if (key.length() == prefix.length())
    108                {
    109                    modifiedKey = "";
    110                }
    111                else
    112                {
    113                    int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
    114                    modifiedKey = key.substring(i);
    115                }
    116
    117                return modifiedKey;
    118            }
    119        }
    120
    121        /**
    122         * Return the parent configuration for this subset.
    123         *
    124         * @return the parent configuration
    125         */
    126        public Configuration getParent()
    127        {
    128            return parent;
    129        }
    130
    131        /**
    132         * Return the prefix used to select the properties in the parent configuration.
    133         *
    134         * @return the prefix used by this subset
    135         */
    136        public String getPrefix()
    137        {
    138            return prefix;
    139        }
    140
    141        /**
    142         * Set the prefix used to select the properties in the parent configuration.
    143         *
    144         * @param prefix the prefix
    145         */
    146        public void setPrefix(String prefix)
    147        {
    148            this.prefix = prefix;
    149        }
    150
    151        @Override
    152        public Configuration subset(String prefix)
    153        {
    154            return parent.subset(getParentKey(prefix));
    155        }
    156
    157        public boolean isEmpty()
    158        {
    159            return !getKeys().hasNext();
    160        }
    161
    162        public boolean containsKey(String key)
    163        {
    164            return parent.containsKey(getParentKey(key));
    165        }
    166
    167        @Override
    168        public void addPropertyDirect(String key, Object value)
    169        {
    170            parent.addProperty(getParentKey(key), value);
    171        }
    172
    173        @Override
    174        protected void clearPropertyDirect(String key)
    175        {
    176            parent.clearProperty(getParentKey(key));
    177        }
    178
    179        public Object getProperty(String key)
    180        {
    181            return parent.getProperty(getParentKey(key));
    182        }
    183
    184        @Override
    185        public Iterator<String> getKeys(String prefix)
    186        {
    187            return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
    188        }
    189
    190        public Iterator<String> getKeys()
    191        {
    192            return new SubsetIterator(parent.getKeys(prefix));
    193        }
    194
    195        @Override
    196        protected Object interpolate(Object base)
    197        {
    198            if (delimiter == null && "".equals(prefix))
    199            {
    200                return super.interpolate(base);
    201            }
    202            else
    203            {
    204                SubsetConfiguration config = new SubsetConfiguration(parent, "");
    205                ConfigurationInterpolator interpolator = config.getInterpolator();
    206                getInterpolator().registerLocalLookups(interpolator);
    207                if (parent instanceof AbstractConfiguration)
    208                {
    209                    interpolator.setParentInterpolator(((AbstractConfiguration) parent).getInterpolator());
    210                }
    211                return config.interpolate(base);
    212            }
    213        }
    214
    215        @Override
    216        protected String interpolate(String base)
    217        {
    218            return super.interpolate(base);
    219        }
    220
    221        /**
    222         * {@inheritDoc}
    223         *
    224         * Change the behavior of the parent configuration if it supports this feature.
    225         */
    226        @Override
    227        public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
    228        {
    229            if (parent instanceof AbstractConfiguration)
    230            {
    231                ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
    232            }
    233            else
    234            {
    235                super.setThrowExceptionOnMissing(throwExceptionOnMissing);
    236            }
    237        }
    238
    239        /**
    240         * {@inheritDoc}
    241         *
    242         * The subset inherits this feature from its parent if it supports this feature.
    243         */
    244        @Override
    245        public boolean isThrowExceptionOnMissing()
    246        {
    247            if (parent instanceof AbstractConfiguration)
    248            {
    249                return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
    250            }
    251            else
    252            {
    253                return super.isThrowExceptionOnMissing();
    254            }
    255        }
    256
    257        /**
    258         * Returns the list delimiter. This property will be fetched from the parent
    259         * configuration if supported.
    260         *
    261         * @return the list delimiter
    262         * @since 1.4
    263         */
    264        @Override
    265        public char getListDelimiter()
    266        {
    267            return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
    268                    .getListDelimiter()
    269                    : super.getListDelimiter();
    270        }
    271
    272        /**
    273         * Sets the list delimiter. If the parent configuration supports this
    274         * feature, the delimiter will be set at the parent.
    275         *
    276         * @param delim the new list delimiter
    277         * @since 1.4
    278         */
    279        @Override
    280        public void setListDelimiter(char delim)
    281        {
    282            if (parent instanceof AbstractConfiguration)
    283            {
    284                ((AbstractConfiguration) parent).setListDelimiter(delim);
    285            }
    286            else
    287            {
    288                super.setListDelimiter(delim);
    289            }
    290        }
    291
    292        /**
    293         * Returns a flag whether string properties should be checked for list
    294         * delimiter characters. This implementation ensures that this flag is kept
    295         * in sync with the parent configuration if this object supports this
    296         * feature.
    297         *
    298         * @return the delimiter parsing disabled flag
    299         * @since 1.4
    300         */
    301        @Override
    302        public boolean isDelimiterParsingDisabled()
    303        {
    304            return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
    305                    .isDelimiterParsingDisabled()
    306                    : super.isDelimiterParsingDisabled();
    307        }
    308
    309        /**
    310         * Sets a flag whether list parsing is disabled. This implementation will
    311         * also set the flag at the parent configuration if this object supports
    312         * this feature.
    313         *
    314         * @param delimiterParsingDisabled the delimiter parsing disabled flag
    315         * @since 1.4
    316         */
    317        @Override
    318        public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
    319        {
    320            if (parent instanceof AbstractConfiguration)
    321            {
    322                ((AbstractConfiguration) parent)
    323                        .setDelimiterParsingDisabled(delimiterParsingDisabled);
    324            }
    325            else
    326            {
    327                super.setDelimiterParsingDisabled(delimiterParsingDisabled);
    328            }
    329        }
    330
    331
    332        /**
    333         * A specialized iterator to be returned by the {@code getKeys()}
    334         * methods. This implementation wraps an iterator from the parent
    335         * configuration. The keys returned by this iterator are correspondingly
    336         * transformed.
    337         */
    338        private class SubsetIterator implements Iterator<String>
    339        {
    340            /** Stores the wrapped iterator. */
    341            private final Iterator<String> parentIterator;
    342
    343            /**
    344             * Creates a new instance of {@code SubsetIterator} and
    345             * initializes it with the parent iterator.
    346             *
    347             * @param it the iterator of the parent configuration
    348             */
    349            public SubsetIterator(Iterator<String> it)
    350            {
    351                parentIterator = it;
    352            }
    353
    354            /**
    355             * Checks whether there are more elements. Delegates to the parent
    356             * iterator.
    357             *
    358             * @return a flag whether there are more elements
    359             */
    360            public boolean hasNext()
    361            {
    362                return parentIterator.hasNext();
    363            }
    364
    365            /**
    366             * Returns the next element in the iteration. This is the next key from
    367             * the parent configuration, transformed to correspond to the point of
    368             * view of this subset configuration.
    369             *
    370             * @return the next element
    371             */
    372            public String next()
    373            {
    374                return getChildKey(parentIterator.next());
    375            }
    376
    377            /**
    378             * Removes the current element from the iteration. Delegates to the
    379             * parent iterator.
    380             */
    381            public void remove()
    382            {
    383                parentIterator.remove();
    384            }
    385        }
    386    }