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