    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.ArrayList;
    021    import java.util.Collection;
    022    import java.util.Iterator;
    023    import java.util.LinkedList;
    024    import java.util.List;
    025    import java.util.ListIterator;
    026
    027    /**
    028     * This Configuration class allows you to add multiple different types of Configuration
    029     * to this CompositeConfiguration.  If you add Configuration1, and then Configuration2,
    030     * any properties shared will mean that Configuration1 will be returned.
    031     * You can add multiple different types or the same type of properties file.
    032     * If Configuration1 doesn't have the property, then Configuration2 will be checked.
    033     *
    034     * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
    035     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    036     * @version $Id: CompositeConfiguration.java 705028 2008-10-15 20:33:35Z oheger $
    037     */
    038    public class CompositeConfiguration extends AbstractConfiguration
    039    implements Cloneable
    040    {
    041        /** List holding all the configuration */
    042        private List configList = new LinkedList();
    043
    044        /**
    045         * Configuration that holds in memory stuff.  Inserted as first so any
    046         * setProperty() override anything else added.
    047         */
    048        private Configuration inMemoryConfiguration;
    049
    050        /**
    051         * Creates an empty CompositeConfiguration object which can then
    052         * be added some other Configuration files
    053         */
    054        public CompositeConfiguration()
    055        {
    056            clear();
    057        }
    058
    059        /**
    060         * Creates a CompositeConfiguration object with a specified in memory
    061         * configuration. This configuration will store any changes made to
    062         * the CompositeConfiguration.
    063         *
    064         * @param inMemoryConfiguration the in memory configuration to use
    065         */
    066        public CompositeConfiguration(Configuration inMemoryConfiguration)
    067        {
    068            configList.clear();
    069            this.inMemoryConfiguration = inMemoryConfiguration;
    070            configList.add(inMemoryConfiguration);
    071        }
    072
    073        /**
    074         * Create a CompositeConfiguration with an empty in memory configuration
    075         * and adds the collection of configurations specified.
    076         *
    077         * @param configurations the collection of configurations to add
    078         */
    079        public CompositeConfiguration(Collection configurations)
    080        {
    081            this(new BaseConfiguration(), configurations);
    082        }
    083
    084        /**
    085         * Creates a CompositeConfiguration with a specified in memory
    086         * configuration, and then adds the given collection of configurations.
    087         *
    088         * @param inMemoryConfiguration the in memory configuration to use
    089         * @param configurations        the collection of configurations to add
    090         */
    091        public CompositeConfiguration(Configuration inMemoryConfiguration, Collection configurations)
    092        {
    093            this(inMemoryConfiguration);
    094
    095            if (configurations != null)
    096            {
    097                Iterator it = configurations.iterator();
    098                while (it.hasNext())
    099                {
    100                    addConfiguration((Configuration) it.next());
    101                }
    102            }
    103        }
    104
    105        /**
    106         * Add a configuration.
    107         *
    108         * @param config the configuration to add
    109         */
    110        public void addConfiguration(Configuration config)
    111        {
    112            if (!configList.contains(config))
    113            {
    114                // As the inMemoryConfiguration contains all manually added keys,
    115                // we must make sure that it is always last. "Normal", non composed
    116                // configuration add their keys at the end of the configuration and
    117                // we want to mimic this behaviour.
    118                configList.add(configList.indexOf(inMemoryConfiguration), config);
    119
    120                if (config instanceof AbstractConfiguration)
    121                {
    122                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    123                }
    124            }
    125        }
    126
    127        /**
    128         * Remove a configuration. The in memory configuration cannot be removed.
    129         *
    130         * @param config The configuration to remove
    131         */
    132        public void removeConfiguration(Configuration config)
    133        {
    134            // Make sure that you can't remove the inMemoryConfiguration from
    135            // the CompositeConfiguration object
    136            if (!config.equals(inMemoryConfiguration))
    137            {
    138                configList.remove(config);
    139            }
    140        }
    141
    142        /**
    143         * Return the number of configurations.
    144         *
    145         * @return the number of configuration
    146         */
    147        public int getNumberOfConfigurations()
    148        {
    149            return configList.size();
    150        }
    151
    152        /**
    153         * Remove all configuration reinitialize the in memory configuration.
    154         */
    155        public void clear()
    156        {
    157            configList.clear();
    158            // recreate the in memory configuration
    159            inMemoryConfiguration = new BaseConfiguration();
    160            ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    161            ((BaseConfiguration) inMemoryConfiguration).setListDelimiter(getListDelimiter());
    162            ((BaseConfiguration) inMemoryConfiguration).setDelimiterParsingDisabled(isDelimiterParsingDisabled());
    163            configList.add(inMemoryConfiguration);
    164        }
    165
    166        /**
    167         * Add this property to the inmemory Configuration.
    168         *
    169         * @param key The Key to add the property to.
    170         * @param token The Value to add.
    171         */
    172        protected void addPropertyDirect(String key, Object token)
    173        {
    174            inMemoryConfiguration.addProperty(key, token);
    175        }
    176
    177        /**
    178         * Read property from underlying composite
    179         *
    180         * @param key key to use for mapping
    181         *
    182         * @return object associated with the given configuration key.
    183         */
    184        public Object getProperty(String key)
    185        {
    186            Configuration firstMatchingConfiguration = null;
    187            for (Iterator i = configList.iterator(); i.hasNext();)
    188            {
    189                Configuration config = (Configuration) i.next();
    190                if (config.containsKey(key))
    191                {
    192                    firstMatchingConfiguration = config;
    193                    break;
    194                }
    195            }
    196
    197            if (firstMatchingConfiguration != null)
    198            {
    199                return firstMatchingConfiguration.getProperty(key);
    200            }
    201            else
    202            {
    203                return null;
    204            }
    205        }
    206
    207        public Iterator getKeys()
    208        {
    209            List keys = new ArrayList();
    210            for (Iterator i = configList.iterator(); i.hasNext();)
    211            {
    212                Configuration config = (Configuration) i.next();
    213
    214                Iterator j = config.getKeys();
    215                while (j.hasNext())
    216                {
    217                    String key = (String) j.next();
    218                    if (!keys.contains(key))
    219                    {
    220                        keys.add(key);
    221                    }
    222                }
    223            }
    224
    225            return keys.iterator();
    226        }
    227
    228        public Iterator getKeys(String key)
    229        {
    230            List keys = new ArrayList();
    231            for (Iterator i = configList.iterator(); i.hasNext();)
    232            {
    233                Configuration config = (Configuration) i.next();
    234
    235                Iterator j = config.getKeys(key);
    236                while (j.hasNext())
    237                {
    238                    String newKey = (String) j.next();
    239                    if (!keys.contains(newKey))
    240                    {
    241                        keys.add(newKey);
    242                    }
    243                }
    244            }
    245
    246            return keys.iterator();
    247        }
    248
    249        public boolean isEmpty()
    250        {
    251            boolean isEmpty = true;
    252            for (Iterator i = configList.iterator(); i.hasNext();)
    253            {
    254                Configuration config = (Configuration) i.next();
    255                if (!config.isEmpty())
    256                {
    257                    return false;
    258                }
    259            }
    260
    261            return isEmpty;
    262        }
    263
    264        protected void clearPropertyDirect(String key)
    265        {
    266            for (Iterator i = configList.iterator(); i.hasNext();)
    267            {
    268                Configuration config = (Configuration) i.next();
    269                config.clearProperty(key);
    270            }
    271        }
    272
    273        public boolean containsKey(String key)
    274        {
    275            for (Iterator i = configList.iterator(); i.hasNext();)
    276            {
    277                Configuration config = (Configuration) i.next();
    278                if (config.containsKey(key))
    279                {
    280                    return true;
    281                }
    282            }
    283            return false;
    284        }
    285
    286        public List getList(String key, List defaultValue)
    287        {
    288            List list = new ArrayList();
    289
    290            // add all elements from the first configuration containing the requested key
    291            Iterator it = configList.iterator();
    292            while (it.hasNext() && list.isEmpty())
    293            {
    294                Configuration config = (Configuration) it.next();
    295                if (config != inMemoryConfiguration && config.containsKey(key))
    296                {
    297                    appendListProperty(list, config, key);
    298                }
    299            }
    300
    301            // add all elements from the in memory configuration
    302            appendListProperty(list, inMemoryConfiguration, key);
    303
    304            if (list.isEmpty())
    305            {
    306                return defaultValue;
    307            }
    308
    309            ListIterator lit = list.listIterator();
    310            while (lit.hasNext())
    311            {
    312                lit.set(interpolate(lit.next()));
    313            }
    314
    315            return list;
    316        }
    317
    318        public String[] getStringArray(String key)
    319        {
    320            List list = getList(key);
    321
    322            // transform property values into strings
    323            String[] tokens = new String[list.size()];
    324
    325            for (int i = 0; i < tokens.length; i++)
    326            {
    327                tokens[i] = String.valueOf(list.get(i));
    328            }
    329
    330            return tokens;
    331        }
    332
    333        /**
    334         * Return the configuration at the specified index.
    335         *
    336         * @param index The index of the configuration to retrieve
    337         * @return the configuration at this index
    338         */
    339        public Configuration getConfiguration(int index)
    340        {
    341            return (Configuration) configList.get(index);
    342        }
    343
    344        /**
    345         * Returns the &quot;in memory configuration&quot;. In this configuration
    346         * changes are stored.
    347         *
    348         * @return the in memory configuration
    349         */
    350        public Configuration getInMemoryConfiguration()
    351        {
    352            return inMemoryConfiguration;
    353        }
    354
    355        /**
    356         * Returns a copy of this object. This implementation will create a deep
    357         * clone, i.e. all configurations contained in this composite will also be
    358         * cloned. This only works if all contained configurations support cloning;
    359         * otherwise a runtime exception will be thrown. Registered event handlers
    360         * won't get cloned.
    361         *
    362         * @return the copy
    363         * @since 1.3
    364         */
    365        public Object clone()
    366        {
    367            try
    368            {
    369                CompositeConfiguration copy = (CompositeConfiguration) super
    370                        .clone();
    371                copy.clearConfigurationListeners();
    372                copy.configList = new LinkedList();
    373                copy.inMemoryConfiguration = ConfigurationUtils
    374                        .cloneConfiguration(getInMemoryConfiguration());
    375                copy.configList.add(copy.inMemoryConfiguration);
    376
    377                for (int i = 0; i < getNumberOfConfigurations(); i++)
    378                {
    379                    Configuration config = getConfiguration(i);
    380                    if (config != getInMemoryConfiguration())
    381                    {
    382                        copy.addConfiguration(ConfigurationUtils
    383                                .cloneConfiguration(config));
    384                    }
    385                }
    386
    387                return copy;
    388            }
    389            catch (CloneNotSupportedException cnex)
    390            {
    391                // cannot happen
    392                throw new ConfigurationRuntimeException(cnex);
    393            }
    394        }
    395
    396        /**
    397         * Sets a flag whether added values for string properties should be checked
    398         * for the list delimiter. This implementation ensures that the in memory
    399         * configuration is correctly initialized.
    400         *
    401         * @param delimiterParsingDisabled the new value of the flag
    402         * @since 1.4
    403         */
    404        public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
    405        {
    406            ((BaseConfiguration) getInMemoryConfiguration())
    407                    .setDelimiterParsingDisabled(delimiterParsingDisabled);
    408            super.setDelimiterParsingDisabled(delimiterParsingDisabled);
    409        }
    410
    411        /**
    412         * Sets the character that is used as list delimiter. This implementation
    413         * ensures that the in memory configuration is correctly initialized.
    414         *
    415         * @param listDelimiter the new list delimiter character
    416         * @since 1.4
    417         */
    418        public void setListDelimiter(char listDelimiter)
    419        {
    420            ((BaseConfiguration) getInMemoryConfiguration())
    421                    .setListDelimiter(listDelimiter);
    422            super.setListDelimiter(listDelimiter);
    423        }
    424
    425        /**
    426         * Returns the configuration source, in which the specified key is defined.
    427         * This method will iterate over all existing child configurations and check
    428         * whether they contain the specified key. The following constellations are
    429         * possible:
    430         * <ul>
    431         * <li>If exactly one child configuration contains the key, this
    432         * configuration is returned as the source configuration. This may be the
    433         * <em>in memory configuration</em> (this has to be explicitly checked by
    434         * the calling application).</li>
    435         * <li>If none of the child configurations contain the key, <b>null</b> is
    436         * returned.</li>
    437         * <li>If the key is contained in multiple child configurations or if the
    438         * key is <b>null</b>, a <code>IllegalArgumentException</code> is thrown.
    439         * In this case the source configuration cannot be determined.</li>
    440         * </ul>
    441         *
    442         * @param key the key to be checked
    443         * @return the source configuration of this key
    444         * @throws IllegalArgumentException if the source configuration cannot be
    445         * determined
    446         * @since 1.5
    447         */
    448        public Configuration getSource(String key)
    449        {
    450            if (key == null)
    451            {
    452                throw new IllegalArgumentException("Key must not be null!");
    453            }
    454
    455            Configuration source = null;
    456            for (Iterator it = configList.iterator(); it.hasNext();)
    457            {
    458                Configuration conf = (Configuration) it.next();
    459                if (conf.containsKey(key))
    460                {
    461                    if (source != null)
    462                    {
    463                        throw new IllegalArgumentException("The key " + key
    464                                + " is defined by multiple sources!");
    465                    }
    466                    source = conf;
    467                }
    468            }
    469
    470            return source;
    471        }
    472
    473        /**
    474         * Adds the value of a property to the given list. This method is used by
    475         * <code>getList()</code> for gathering property values from the child
    476         * configurations.
    477         *
    478         * @param dest the list for collecting the data
    479         * @param config the configuration to query
    480         * @param key the key of the property
    481         */
    482        private static void appendListProperty(List dest, Configuration config,
    483                String key)
    484        {
    485            Object value = config.getProperty(key);
    486            if (value != null)
    487            {
    488                if (value instanceof Collection)
    489                {
    490                    dest.addAll((Collection) value);
    491                }
    492                else
    493                {
    494                    dest.add(value);
    495                }
    496            }
    497        }
    498    }