    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.lang.reflect.Array;
    021    import java.math.BigDecimal;
    022    import java.math.BigInteger;
    023    import java.util.ArrayList;
    024    import java.util.Arrays;
    025    import java.util.Collection;
    026    import java.util.Collections;
    027    import java.util.Iterator;
    028    import java.util.List;
    029    import java.util.NoSuchElementException;
    030    import java.util.Properties;
    031
    032    import org.apache.commons.collections.Predicate;
    033    import org.apache.commons.collections.iterators.FilterIterator;
    034    import org.apache.commons.configuration.event.ConfigurationErrorEvent;
    035    import org.apache.commons.configuration.event.ConfigurationErrorListener;
    036    import org.apache.commons.configuration.event.EventSource;
    037    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
    038    import org.apache.commons.lang.BooleanUtils;
    039    import org.apache.commons.lang.ClassUtils;
    040    import org.apache.commons.lang.text.StrLookup;
    041    import org.apache.commons.lang.text.StrSubstitutor;
    042    import org.apache.commons.logging.Log;
    043    import org.apache.commons.logging.impl.NoOpLog;
    044
    045    /**
    046     * <p>Abstract configuration class. Provides basic functionality but does not
    047     * store any data.</p>
    048     * <p>If you want to write your own Configuration class then you should
    049     * implement only abstract methods from this class. A lot of functionality
    050     * needed by typical implementations of the <code>Configuration</code>
    051     * interface is already provided by this base class. Following is a list of
    052     * features implemented here:
    053     * <ul><li>Data conversion support. The various data types required by the
    054     * <code>Configuration</code> interface are already handled by this base class.
    055     * A concrete sub class only needs to provide a generic <code>getProperty()</code>
    056     * method.</li>
    057     * <li>Support for variable interpolation. Property values containing special
    058     * variable tokens (like <code>${var}</code>) will be replaced by their
    059     * corresponding values.</li>
    060     * <li>Support for string lists. The values of properties to be added to this
    061     * configuration are checked whether they contain a list delimiter character. If
    062     * this is the case and if list splitting is enabled, the string is split and
    063     * multiple values are added for this property. (With the
    064     * <code>setListDelimiter()</code> method the delimiter character can be
    065     * specified; per default a comma is used. The
    066     * <code>setDelimiterParsingDisabled()</code> method can be used to disable
    067     * list splitting completely.)</li>
    068     * <li>Allows to specify how missing properties are treated. Per default the
    069     * get methods returning an object will return <b>null</b> if the searched
    070     * property key is not found (and no default value is provided). With the
    071     * <code>setThrowExceptionOnMissing()</code> method this behavior can be
    072     * changed to throw an exception when a requested property cannot be found.</li>
    073     * <li>Basic event support. Whenever this configuration is modified registered
    074     * event listeners are notified. Refer to the various <code>EVENT_XXX</code>
    075     * constants to get an impression about which event types are supported.</li>
    076     * </ul></p>
    077     *
    078     * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov </a>
    079     * @author Oliver Heger
    080     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen </a>
    081     * @version $Id: AbstractConfiguration.java 1153984 2011-08-04 19:57:03Z oheger $
    082     */
    083    public abstract class AbstractConfiguration extends EventSource implements Configuration
    084    {
    085        /**
    086         * Constant for the add property event type.
    087         * @since 1.3
    088         */
    089        public static final int EVENT_ADD_PROPERTY = 1;
    090
    091        /**
    092         * Constant for the clear property event type.
    093         * @since 1.3
    094         */
    095        public static final int EVENT_CLEAR_PROPERTY = 2;
    096
    097        /**
    098         * Constant for the set property event type.
    099         * @since 1.3
    100         */
    101        public static final int EVENT_SET_PROPERTY = 3;
    102
    103        /**
    104         * Constant for the clear configuration event type.
    105         * @since 1.3
    106         */
    107        public static final int EVENT_CLEAR = 4;
    108
    109        /**
    110         * Constant for the get property event type. This event type is used for
    111         * error events.
    112         * @since 1.4
    113         */
    114        public static final int EVENT_READ_PROPERTY = 5;
    115
    116        /** start token */
    117        protected static final String START_TOKEN = "${";
    118
    119        /** end token */
    120        protected static final String END_TOKEN = "}";
    121
    122        /**
    123         * Constant for the disabled list delimiter. This character is passed to the
    124         * list parsing methods if delimiter parsing is disabled. So this character
    125         * should not occur in string property values.
    126         */
    127        private static final char DISABLED_DELIMITER = '\0';
    128
    129        /** The default value for listDelimiter */
    130        private static char defaultListDelimiter = ',';
    131
    132        /** Delimiter used to convert single values to lists */
    133        private char listDelimiter = defaultListDelimiter;
    134
    135        /**
    136         * When set to true the given configuration delimiter will not be used
    137         * while parsing for this configuration.
    138         */
    139        private boolean delimiterParsingDisabled;
    140
    141        /**
    142         * Whether the configuration should throw NoSuchElementExceptions or simply
    143         * return null when a property does not exist. Defaults to return null.
    144         */
    145        private boolean throwExceptionOnMissing;
    146
    147        /** Stores a reference to the object that handles variable interpolation.*/
    148        private StrSubstitutor substitutor;
    149
    150        /** Stores the logger.*/
    151        private Log log;
    152
    153        /**
    154         * Creates a new instance of <code>AbstractConfiguration</code>.
    155         */
    156        public AbstractConfiguration()
    157        {
    158            setLogger(null);
    159        }
    160
    161        /**
    162         * For configurations extending AbstractConfiguration, allow them to change
    163         * the listDelimiter from the default comma (","). This value will be used
    164         * only when creating new configurations. Those already created will not be
    165         * affected by this change
    166         *
    167         * @param delimiter The new listDelimiter
    168         */
    169        public static void setDefaultListDelimiter(char delimiter)
    170        {
    171            AbstractConfiguration.defaultListDelimiter = delimiter;
    172        }
    173
    174        /**
    175         * Sets the default list delimiter.
    176         *
    177         * @param delimiter the delimiter character
    178         * @deprecated Use AbstractConfiguration.setDefaultListDelimiter(char)
    179         * instead
    180         */
    181        public static void setDelimiter(char delimiter)
    182        {
    183            setDefaultListDelimiter(delimiter);
    184        }
    185
    186        /**
    187         * Retrieve the current delimiter. By default this is a comma (",").
    188         *
    189         * @return The delimiter in use
    190         */
    191        public static char getDefaultListDelimiter()
    192        {
    193            return AbstractConfiguration.defaultListDelimiter;
    194        }
    195
    196        /**
    197         * Returns the default list delimiter.
    198         *
    199         * @return the default list delimiter
    200         * @deprecated Use AbstractConfiguration.getDefaultListDelimiter() instead
    201         */
    202        public static char getDelimiter()
    203        {
    204            return getDefaultListDelimiter();
    205        }
    206
    207        /**
    208         * Change the list delimiter for this configuration.
    209         *
    210         * Note: this change will only be effective for new parsings. If you
    211         * want it to take effect for all loaded properties use the no arg constructor
    212         * and call this method before setting the source.
    213         *
    214         * @param listDelimiter The new listDelimiter
    215         */
    216        public void setListDelimiter(char listDelimiter)
    217        {
    218            this.listDelimiter = listDelimiter;
    219        }
    220
    221        /**
    222         * Retrieve the delimiter for this configuration. The default
    223         * is the value of defaultListDelimiter.
    224         *
    225         * @return The listDelimiter in use
    226         */
    227        public char getListDelimiter()
    228        {
    229            return listDelimiter;
    230        }
    231
    232        /**
    233         * Determine if this configuration is using delimiters when parsing
    234         * property values to convert them to lists of values. Defaults to false
    235         * @return true if delimiters are not being used
    236         */
    237        public boolean isDelimiterParsingDisabled()
    238        {
    239            return delimiterParsingDisabled;
    240        }
    241
    242        /**
    243         * Set whether this configuration should use delimiters when parsing
    244         * property values to convert them to lists of values. By default delimiter
    245         * parsing is enabled
    246         *
    247         * Note: this change will only be effective for new parsings. If you
    248         * want it to take effect for all loaded properties use the no arg constructor
    249         * and call this method before setting source.
    250         * @param delimiterParsingDisabled a flag whether delimiter parsing should
    251         * be disabled
    252         */
    253        public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
    254        {
    255            this.delimiterParsingDisabled = delimiterParsingDisabled;
    256        }
    257
    258        /**
    259         * Allows to set the <code>throwExceptionOnMissing</code> flag. This
    260         * flag controls the behavior of property getter methods that return
    261         * objects if the requested property is missing. If the flag is set to
    262         * <b>false</b> (which is the default value), these methods will return
    263         * <b>null</b>. If set to <b>true</b>, they will throw a
    264         * <code>NoSuchElementException</code> exception. Note that getter methods
    265         * for primitive data types are not affected by this flag.
    266         *
    267         * @param throwExceptionOnMissing The new value for the property
    268         */
    269        public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
    270        {
    271            this.throwExceptionOnMissing = throwExceptionOnMissing;
    272        }
    273
    274        /**
    275         * Returns true if missing values throw Exceptions.
    276         *
    277         * @return true if missing values throw Exceptions
    278         */
    279        public boolean isThrowExceptionOnMissing()
    280        {
    281            return throwExceptionOnMissing;
    282        }
    283
    284        /**
    285         * Returns the object that is responsible for variable interpolation.
    286         *
    287         * @return the object responsible for variable interpolation
    288         * @since 1.4
    289         */
    290        public synchronized StrSubstitutor getSubstitutor()
    291        {
    292            if (substitutor == null)
    293            {
    294                substitutor = new StrSubstitutor(createInterpolator());
    295            }
    296            return substitutor;
    297        }
    298
    299        /**
    300         * Returns the <code>ConfigurationInterpolator</code> object that manages
    301         * the lookup objects for resolving variables. <em>Note:</em> If this
    302         * object is manipulated (e.g. new lookup objects added), synchronisation
    303         * has to be manually ensured. Because
    304         * <code>ConfigurationInterpolator</code> is not thread-safe concurrent
    305         * access to properties of this configuration instance (which causes the
    306         * interpolator to be invoked) may cause race conditions.
    307         *
    308         * @return the <code>ConfigurationInterpolator</code> associated with this
    309         * configuration
    310         * @since 1.4
    311         */
    312        public ConfigurationInterpolator getInterpolator()
    313        {
    314            return (ConfigurationInterpolator) getSubstitutor()
    315                    .getVariableResolver();
    316        }
    317
    318        /**
    319         * Creates the interpolator object that is responsible for variable
    320         * interpolation. This method is invoked on first access of the
    321         * interpolation features. It creates a new instance of
    322         * <code>ConfigurationInterpolator</code> and sets the default lookup
    323         * object to an implementation that queries this configuration.
    324         *
    325         * @return the newly created interpolator object
    326         * @since 1.4
    327         */
    328        protected ConfigurationInterpolator createInterpolator()
    329        {
    330            ConfigurationInterpolator interpol = new ConfigurationInterpolator();
    331            interpol.setDefaultLookup(new StrLookup()
    332            {
    333                public String lookup(String var)
    334                {
    335                    Object prop = resolveContainerStore(var);
    336                    return (prop != null) ? prop.toString() : null;
    337                }
    338            });
    339            return interpol;
    340        }
    341
    342        /**
    343         * Returns the logger used by this configuration object.
    344         *
    345         * @return the logger
    346         * @since 1.4
    347         */
    348        public Log getLogger()
    349        {
    350            return log;
    351        }
    352
    353        /**
    354         * Allows to set the logger to be used by this configuration object. This
    355         * method makes it possible for clients to exactly control logging behavior.
    356         * Per default a logger is set that will ignore all log messages. Derived
    357         * classes that want to enable logging should call this method during their
    358         * initialization with the logger to be used.
    359         *
    360         * @param log the new logger
    361         * @since 1.4
    362         */
    363        public void setLogger(Log log)
    364        {
    365            this.log = (log != null) ? log : new NoOpLog();
    366        }
    367
    368        /**
    369         * Adds a special
    370         * <code>{@link org.apache.commons.configuration.event.ConfigurationErrorListener}</code>
    371         * object to this configuration that will log all internal errors. This
    372         * method is intended to be used by certain derived classes, for which it is
    373         * known that they can fail on property access (e.g.
    374         * <code>DatabaseConfiguration</code>).
    375         *
    376         * @since 1.4
    377         */
    378        public void addErrorLogListener()
    379        {
    380            addErrorListener(new ConfigurationErrorListener()
    381            {
    382                public void configurationError(ConfigurationErrorEvent event)
    383                {
    384                    getLogger().warn("Internal error", event.getCause());
    385                }
    386            });
    387        }
    388
    389        public void addProperty(String key, Object value)
    390        {
    391            fireEvent(EVENT_ADD_PROPERTY, key, value, true);
    392            addPropertyValues(key, value,
    393                    isDelimiterParsingDisabled() ? DISABLED_DELIMITER
    394                            : getListDelimiter());
    395            fireEvent(EVENT_ADD_PROPERTY, key, value, false);
    396        }
    397
    398        /**
    399         * Adds a key/value pair to the Configuration. Override this method to
    400         * provide write access to underlying Configuration store.
    401         *
    402         * @param key key to use for mapping
    403         * @param value object to store
    404         */
    405        protected abstract void addPropertyDirect(String key, Object value);
    406
    407        /**
    408         * Adds the specified value for the given property. This method supports
    409         * single values and containers (e.g. collections or arrays) as well. In the
    410         * latter case, <code>addPropertyDirect()</code> will be called for each
    411         * element.
    412         *
    413         * @param key the property key
    414         * @param value the value object
    415         * @param delimiter the list delimiter character
    416         */
    417        private void addPropertyValues(String key, Object value, char delimiter)
    418        {
    419            Iterator it = PropertyConverter.toIterator(value, delimiter);
    420            while (it.hasNext())
    421            {
    422                addPropertyDirect(key, it.next());
    423            }
    424        }
    425
    426        /**
    427         * interpolate key names to handle ${key} stuff
    428         *
    429         * @param base string to interpolate
    430         *
    431         * @return returns the key name with the ${key} substituted
    432         */
    433        protected String interpolate(String base)
    434        {
    435            Object result = interpolate((Object) base);
    436            return (result == null) ? null : result.toString();
    437        }
    438
    439        /**
    440         * Returns the interpolated value. Non String values are returned without change.
    441         *
    442         * @param value the value to interpolate
    443         *
    444         * @return returns the value with variables substituted
    445         */
    446        protected Object interpolate(Object value)
    447        {
    448            return PropertyConverter.interpolate(value, this);
    449        }
    450
    451        /**
    452         * Recursive handler for multple levels of interpolation.
    453         *
    454         * When called the first time, priorVariables should be null.
    455         *
    456         * @param base string with the ${key} variables
    457         * @param priorVariables serves two purposes: to allow checking for loops,
    458         * and creating a meaningful exception message should a loop occur. It's
    459         * 0'th element will be set to the value of base from the first call. All
    460         * subsequent interpolated variables are added afterward.
    461         *
    462         * @return the string with the interpolation taken care of
    463         * @deprecated Interpolation is now handled by
    464         * <code>{@link PropertyConverter}</code>; this method will no longer be
    465         * called
    466         */
    467        protected String interpolateHelper(String base, List priorVariables)
    468        {
    469            return base; // just a dummy implementation
    470        }
    471
    472        public Configuration subset(String prefix)
    473        {
    474            return new SubsetConfiguration(this, prefix, ".");
    475        }
    476
    477        public void setProperty(String key, Object value)
    478        {
    479            fireEvent(EVENT_SET_PROPERTY, key, value, true);
    480            setDetailEvents(false);
    481            try
    482            {
    483                clearProperty(key);
    484                addProperty(key, value);
    485            }
    486            finally
    487            {
    488                setDetailEvents(true);
    489            }
    490            fireEvent(EVENT_SET_PROPERTY, key, value, false);
    491        }
    492
    493        /**
    494         * Removes the specified property from this configuration. This
    495         * implementation performs some preparations and then delegates to
    496         * <code>clearPropertyDirect()</code>, which will do the real work.
    497         *
    498         * @param key the key to be removed
    499         */
    500        public void clearProperty(String key)
    501        {
    502            fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
    503            clearPropertyDirect(key);
    504            fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
    505        }
    506
    507        /**
    508         * Removes the specified property from this configuration. This method is
    509         * called by <code>clearProperty()</code> after it has done some
    510         * preparations. It should be overriden in sub classes. This base
    511         * implementation is just left empty.
    512         *
    513         * @param key the key to be removed
    514         */
    515        protected void clearPropertyDirect(String key)
    516        {
    517            // override in sub classes
    518        }
    519
    520        public void clear()
    521        {
    522            fireEvent(EVENT_CLEAR, null, null, true);
    523            setDetailEvents(false);
    524            boolean useIterator = true;
    525            try
    526            {
    527                Iterator it = getKeys();
    528                while (it.hasNext())
    529                {
    530                    String key = (String) it.next();
    531                    if (useIterator)
    532                    {
    533                        try
    534                        {
    535                            it.remove();
    536                        }
    537                        catch (UnsupportedOperationException usoex)
    538                        {
    539                            useIterator = false;
    540                        }
    541                    }
    542
    543                    if (useIterator && containsKey(key))
    544                    {
    545                        useIterator = false;
    546                    }
    547
    548                    if (!useIterator)
    549                    {
    550                        // workaround for Iterators that do not remove the property
    551                        // on calling remove() or do not support remove() at all
    552                        clearProperty(key);
    553                    }
    554                }
    555            }
    556            finally
    557            {
    558                setDetailEvents(true);
    559            }
    560            fireEvent(EVENT_CLEAR, null, null, false);
    561        }
    562
    563        /**
    564         * {@inheritDoc} This implementation returns keys that either match the
    565         * prefix or start with the prefix followed by a dot ('.'). So the call
    566         * <code>getKeys("db");</code> will find the keys <code>db</code>,
    567         * <code>db.user</code>, or <code>db.password</code>, but not the key
    568         * <code>dbdriver</code>.
    569         */
    570        public Iterator getKeys(final String prefix)
    571        {
    572            return new FilterIterator(getKeys(), new Predicate()
    573            {
    574                public boolean evaluate(Object obj)
    575                {
    576                    String key = (String) obj;
    577                    return key.startsWith(prefix + ".") || key.equals(prefix);
    578                }
    579            });
    580        }
    581
    582        public Properties getProperties(String key)
    583        {
    584            return getProperties(key, null);
    585        }
    586
    587        /**
    588         * Get a list of properties associated with the given configuration key.
    589         *
    590         * @param key The configuration key.
    591         * @param defaults Any default values for the returned
    592         * <code>Properties</code> object. Ignored if <code>null</code>.
    593         *
    594         * @return The associated properties if key is found.
    595         *
    596         * @throws ConversionException is thrown if the key maps to an object that
    597         * is not a String/List of Strings.
    598         *
    599         * @throws IllegalArgumentException if one of the tokens is malformed (does
    600         * not contain an equals sign).
    601         */
    602        public Properties getProperties(String key, Properties defaults)
    603        {
    604            /*
    605             * Grab an array of the tokens for this key.
    606             */
    607            String[] tokens = getStringArray(key);
    608
    609            /*
    610             * Each token is of the form 'key=value'.
    611             */
    612            Properties props = defaults == null ? new Properties() : new Properties(defaults);
    613            for (int i = 0; i < tokens.length; i++)
    614            {
    615                String token = tokens[i];
    616                int equalSign = token.indexOf('=');
    617                if (equalSign > 0)
    618                {
    619                    String pkey = token.substring(0, equalSign).trim();
    620                    String pvalue = token.substring(equalSign + 1).trim();
    621                    props.put(pkey, pvalue);
    622                }
    623                else if (tokens.length == 1 && "".equals(token))
    624                {
    625                    // Semantically equivalent to an empty Properties
    626                    // object.
    627                    break;
    628                }
    629                else
    630                {
    631                    throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
    632                }
    633            }
    634            return props;
    635        }
    636
    637        /**
    638         * {@inheritDoc}
    639         * @see PropertyConverter#toBoolean(Object)
    640         */
    641        public boolean getBoolean(String key)
    642        {
    643            Boolean b = getBoolean(key, null);
    644            if (b != null)
    645            {
    646                return b.booleanValue();
    647            }
    648            else
    649            {
    650                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    651            }
    652        }
    653
    654        /**
    655         * {@inheritDoc}
    656         * @see PropertyConverter#toBoolean(Object)
    657         */
    658        public boolean getBoolean(String key, boolean defaultValue)
    659        {
    660            return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue();
    661        }
    662
    663        /**
    664         * Obtains the value of the specified key and tries to convert it into a
    665         * <code>Boolean</code> object. If the property has no value, the passed
    666         * in default value will be used.
    667         *
    668         * @param key the key of the property
    669         * @param defaultValue the default value
    670         * @return the value of this key converted to a <code>Boolean</code>
    671         * @throws ConversionException if the value cannot be converted to a
    672         * <code>Boolean</code>
    673         * @see PropertyConverter#toBoolean(Object)
    674         */
    675        public Boolean getBoolean(String key, Boolean defaultValue)
    676        {
    677            Object value = resolveContainerStore(key);
    678
    679            if (value == null)
    680            {
    681                return defaultValue;
    682            }
    683            else
    684            {
    685                try
    686                {
    687                    return PropertyConverter.toBoolean(interpolate(value));
    688                }
    689                catch (ConversionException e)
    690                {
    691                    throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e);
    692                }
    693            }
    694        }
    695
    696        public byte getByte(String key)
    697        {
    698            Byte b = getByte(key, null);
    699            if (b != null)
    700            {
    701                return b.byteValue();
    702            }
    703            else
    704            {
    705                throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
    706            }
    707        }
    708
    709        public byte getByte(String key, byte defaultValue)
    710        {
    711            return getByte(key, new Byte(defaultValue)).byteValue();
    712        }
    713
    714        public Byte getByte(String key, Byte defaultValue)
    715        {
    716            Object value = resolveContainerStore(key);
    717
    718            if (value == null)
    719            {
    720                return defaultValue;
    721            }
    722            else
    723            {
    724                try
    725                {
    726                    return PropertyConverter.toByte(interpolate(value));
    727                }
    728                catch (ConversionException e)
    729                {
    730                    throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
    731                }
    732            }
    733        }
    734
    735        public double getDouble(String key)
    736        {
    737            Double d = getDouble(key, null);
    738            if (d != null)
    739            {
    740                return d.doubleValue();
    741            }
    742            else
    743            {
    744                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    745            }
    746        }
    747
    748        public double getDouble(String key, double defaultValue)
    749        {
    750            return getDouble(key, new Double(defaultValue)).doubleValue();
    751        }
    752
    753        public Double getDouble(String key, Double defaultValue)
    754        {
    755            Object value = resolveContainerStore(key);
    756
    757            if (value == null)
    758            {
    759                return defaultValue;
    760            }
    761            else
    762            {
    763                try
    764                {
    765                    return PropertyConverter.toDouble(interpolate(value));
    766                }
    767                catch (ConversionException e)
    768                {
    769                    throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
    770                }
    771            }
    772        }
    773
    774        public float getFloat(String key)
    775        {
    776            Float f = getFloat(key, null);
    777            if (f != null)
    778            {
    779                return f.floatValue();
    780            }
    781            else
    782            {
    783                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    784            }
    785        }
    786
    787        public float getFloat(String key, float defaultValue)
    788        {
    789            return getFloat(key, new Float(defaultValue)).floatValue();
    790        }
    791
    792        public Float getFloat(String key, Float defaultValue)
    793        {
    794            Object value = resolveContainerStore(key);
    795
    796            if (value == null)
    797            {
    798                return defaultValue;
    799            }
    800            else
    801            {
    802                try
    803                {
    804                    return PropertyConverter.toFloat(interpolate(value));
    805                }
    806                catch (ConversionException e)
    807                {
    808                    throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
    809                }
    810            }
    811        }
    812
    813        public int getInt(String key)
    814        {
    815            Integer i = getInteger(key, null);
    816            if (i != null)
    817            {
    818                return i.intValue();
    819            }
    820            else
    821            {
    822                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    823            }
    824        }
    825
    826        public int getInt(String key, int defaultValue)
    827        {
    828            Integer i = getInteger(key, null);
    829
    830            if (i == null)
    831            {
    832                return defaultValue;
    833            }
    834
    835            return i.intValue();
    836        }
    837
    838        public Integer getInteger(String key, Integer defaultValue)
    839        {
    840            Object value = resolveContainerStore(key);
    841
    842            if (value == null)
    843            {
    844                return defaultValue;
    845            }
    846            else
    847            {
    848                try
    849                {
    850                    return PropertyConverter.toInteger(interpolate(value));
    851                }
    852                catch (ConversionException e)
    853                {
    854                    throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e);
    855                }
    856            }
    857        }
    858
    859        public long getLong(String key)
    860        {
    861            Long l = getLong(key, null);
    862            if (l != null)
    863            {
    864                return l.longValue();
    865            }
    866            else
    867            {
    868                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    869            }
    870        }
    871
    872        public long getLong(String key, long defaultValue)
    873        {
    874            return getLong(key, new Long(defaultValue)).longValue();
    875        }
    876
    877        public Long getLong(String key, Long defaultValue)
    878        {
    879            Object value = resolveContainerStore(key);
    880
    881            if (value == null)
    882            {
    883                return defaultValue;
    884            }
    885            else
    886            {
    887                try
    888                {
    889                    return PropertyConverter.toLong(interpolate(value));
    890                }
    891                catch (ConversionException e)
    892                {
    893                    throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
    894                }
    895            }
    896        }
    897
    898        public short getShort(String key)
    899        {
    900            Short s = getShort(key, null);
    901            if (s != null)
    902            {
    903                return s.shortValue();
    904            }
    905            else
    906            {
    907                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    908            }
    909        }
    910
    911        public short getShort(String key, short defaultValue)
    912        {
    913            return getShort(key, new Short(defaultValue)).shortValue();
    914        }
    915
    916        public Short getShort(String key, Short defaultValue)
    917        {
    918            Object value = resolveContainerStore(key);
    919
    920            if (value == null)
    921            {
    922                return defaultValue;
    923            }
    924            else
    925            {
    926                try
    927                {
    928                    return PropertyConverter.toShort(interpolate(value));
    929                }
    930                catch (ConversionException e)
    931                {
    932                    throw new ConversionException('\'' + key + "' doesn't map to a Short object", e);
    933                }
    934            }
    935        }
    936
    937        /**
    938         * {@inheritDoc}
    939         * @see #setThrowExceptionOnMissing(boolean)
    940         */
    941        public BigDecimal getBigDecimal(String key)
    942        {
    943            BigDecimal number = getBigDecimal(key, null);
    944            if (number != null)
    945            {
    946                return number;
    947            }
    948            else if (isThrowExceptionOnMissing())
    949            {
    950                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    951            }
    952            else
    953            {
    954                return null;
    955            }
    956        }
    957
    958        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
    959        {
    960            Object value = resolveContainerStore(key);
    961
    962            if (value == null)
    963            {
    964                return defaultValue;
    965            }
    966            else
    967            {
    968                try
    969                {
    970                    return PropertyConverter.toBigDecimal(interpolate(value));
    971                }
    972                catch (ConversionException e)
    973                {
    974                    throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
    975                }
    976            }
    977        }
    978
    979        /**
    980         * {@inheritDoc}
    981         * @see #setThrowExceptionOnMissing(boolean)
    982         */
    983        public BigInteger getBigInteger(String key)
    984        {
    985            BigInteger number = getBigInteger(key, null);
    986            if (number != null)
    987            {
    988                return number;
    989            }
    990            else if (isThrowExceptionOnMissing())
    991            {
    992                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    993            }
    994            else
    995            {
    996                return null;
    997            }
    998        }
    999
    1000        public BigInteger getBigInteger(String key, BigInteger defaultValue)
    1001        {
    1002            Object value = resolveContainerStore(key);
    1003
    1004            if (value == null)
    1005            {
    1006                return defaultValue;
    1007            }
    1008            else
    1009            {
    1010                try
    1011                {
    1012                    return PropertyConverter.toBigInteger(interpolate(value));
    1013                }
    1014                catch (ConversionException e)
    1015                {
    1016                    throw new ConversionException('\'' + key + "' doesn't map to a BigInteger object", e);
    1017                }
    1018            }
    1019        }
    1020
    1021        /**
    1022         * {@inheritDoc}
    1023         * @see #setThrowExceptionOnMissing(boolean)
    1024         */
    1025        public String getString(String key)
    1026        {
    1027            String s = getString(key, null);
    1028            if (s != null)
    1029            {
    1030                return s;
    1031            }
    1032            else if (isThrowExceptionOnMissing())
    1033            {
    1034                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
    1035            }
    1036            else
    1037            {
    1038                return null;
    1039            }
    1040        }
    1041
    1042        public String getString(String key, String defaultValue)
    1043        {
    1044            Object value = resolveContainerStore(key);
    1045
    1046            if (value instanceof String)
    1047            {
    1048                return interpolate((String) value);
    1049            }
    1050            else if (value == null)
    1051            {
    1052                return interpolate(defaultValue);
    1053            }
    1054            else
    1055            {
    1056                throw new ConversionException('\'' + key + "' doesn't map to a String object");
    1057            }
    1058        }
    1059
    1060        /**
    1061         * Get an array of strings associated with the given configuration key.
    1062         * If the key doesn't map to an existing object, an empty array is returned.
    1063         * If a property is added to a configuration, it is checked whether it
    1064         * contains multiple values. This is obvious if the added object is a list
    1065         * or an array. For strings it is checked whether the string contains the
    1066         * list delimiter character that can be specified using the
    1067         * <code>setListDelimiter()</code> method. If this is the case, the string
    1068         * is splitted at these positions resulting in a property with multiple
    1069         * values.
    1070         *
    1071         * @param key The configuration key.
    1072         * @return The associated string array if key is found.
    1073         *
    1074         * @throws ConversionException is thrown if the key maps to an
    1075         *         object that is not a String/List of Strings.
    1076         * @see #setListDelimiter(char)
    1077         * @see #setDelimiterParsingDisabled(boolean)
    1078         */
    1079        public String[] getStringArray(String key)
    1080        {
    1081            Object value = getProperty(key);
    1082
    1083            String[] array;
    1084
    1085            if (value instanceof String)
    1086            {
    1087                array = new String[1];
    1088
    1089                array[0] = interpolate((String) value);
    1090            }
    1091            else if (value instanceof List)
    1092            {
    1093                List list = (List) value;
    1094                array = new String[list.size()];
    1095
    1096                for (int i = 0; i < array.length; i++)
    1097                {
    1098                    array[i] = interpolate((String) list.get(i));
    1099                }
    1100            }
    1101            else if (value == null)
    1102            {
    1103                array = new String[0];
    1104            }
    1105            else if (isScalarValue(value))
    1106            {
    1107                array = new String[1];
    1108                array[0] = value.toString();
    1109            }
    1110            else
    1111            {
    1112                throw new ConversionException('\'' + key + "' doesn't map to a String/List object");
    1113            }
    1114            return array;
    1115        }
    1116
    1117        /**
    1118         * {@inheritDoc}
    1119         * @see #getStringArray(String)
    1120         */
    1121        public List getList(String key)
    1122        {
    1123            return getList(key, new ArrayList());
    1124        }
    1125
    1126        public List getList(String key, List defaultValue)
    1127        {
    1128            Object value = getProperty(key);
    1129            List list;
    1130
    1131            if (value instanceof String)
    1132            {
    1133                list = new ArrayList(1);
    1134                list.add(interpolate((String) value));
    1135            }
    1136            else if (value instanceof List)
    1137            {
    1138                list = new ArrayList();
    1139                List l = (List) value;
    1140
    1141                // add the interpolated elements in the new list
    1142                Iterator it = l.iterator();
    1143                while (it.hasNext())
    1144                {
    1145                    list.add(interpolate(it.next()));
    1146                }
    1147            }
    1148            else if (value == null)
    1149            {
    1150                list = defaultValue;
    1151            }
    1152            else if (value.getClass().isArray())
    1153            {
    1154                return Arrays.asList((Object[]) value);
    1155            }
    1156            else if (isScalarValue(value))
    1157            {
    1158                return Collections.singletonList(value.toString());
    1159            }
    1160            else
    1161            {
    1162                throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a "
    1163                        + value.getClass().getName());
    1164            }
    1165            return list;
    1166        }
    1167
    1168        /**
    1169         * Returns an object from the store described by the key. If the value is a
    1170         * Collection object, replace it with the first object in the collection.
    1171         *
    1172         * @param key The property key.
    1173         *
    1174         * @return value Value, transparently resolving a possible collection dependency.
    1175         */
    1176        protected Object resolveContainerStore(String key)
    1177        {
    1178            Object value = getProperty(key);
    1179            if (value != null)
    1180            {
    1181                if (value instanceof Collection)
    1182                {
    1183                    Collection collection = (Collection) value;
    1184                    value = collection.isEmpty() ? null : collection.iterator().next();
    1185                }
    1186                else if (value.getClass().isArray() && Array.getLength(value) > 0)
    1187                {
    1188                    value = Array.get(value, 0);
    1189                }
    1190            }
    1191
    1192            return value;
    1193        }
    1194
    1195        /**
    1196         * Checks whether the specified object is a scalar value. This method is
    1197         * called by <code>getList()</code> and <code>getStringArray()</code> if the
    1198         * property requested is not a string, a list, or an array. If it returns
    1199         * <b>true</b>, the calling method transforms the value to a string and
    1200         * returns a list or an array with this single element. This implementation
    1201         * returns <b>true</b> if the value is of a wrapper type for a primitive
    1202         * type.
    1203         *
    1204         * @param value the value to be checked
    1205         * @return a flag whether the value is a scalar
    1206         * @since 1.7
    1207         */
    1208        protected boolean isScalarValue(Object value)
    1209        {
    1210            return ClassUtils.wrapperToPrimitive(value.getClass()) != null;
    1211        }
    1212
    1213        /**
    1214         * Copies the content of the specified configuration into this
    1215         * configuration. If the specified configuration contains a key that is also
    1216         * present in this configuration, the value of this key will be replaced by
    1217         * the new value. <em>Note:</em> This method won't work well when copying
    1218         * hierarchical configurations because it is not able to copy information
    1219         * about the properties' structure (i.e. the parent-child-relationships will
    1220         * get lost). So when dealing with hierarchical configuration objects their
    1221         * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods
    1222         * should be used.
    1223         *
    1224         * @param c the configuration to copy (can be <b>null</b>, then this
    1225         * operation will have no effect)
    1226         * @since 1.5
    1227         */
    1228        public void copy(Configuration c)
    1229        {
    1230            if (c != null)
    1231            {
    1232                for (Iterator it = c.getKeys(); it.hasNext();)
    1233                {
    1234                    String key = (String) it.next();
    1235                    Object value = c.getProperty(key);
    1236                    fireEvent(EVENT_SET_PROPERTY, key, value, true);
    1237                    setDetailEvents(false);
    1238                    try
    1239                    {
    1240                        clearProperty(key);
    1241                        addPropertyValues(key, value, DISABLED_DELIMITER);
    1242                    }
    1243                    finally
    1244                    {
    1245                        setDetailEvents(true);
    1246                    }
    1247                    fireEvent(EVENT_SET_PROPERTY, key, value, false);
    1248                }
    1249            }
    1250        }
    1251
    1252        /**
    1253         * Appends the content of the specified configuration to this configuration.
    1254         * The values of all properties contained in the specified configuration
    1255         * will be appended to this configuration. So if a property is already
    1256         * present in this configuration, its new value will be a union of the
    1257         * values in both configurations. <em>Note:</em> This method won't work
    1258         * well when appending hierarchical configurations because it is not able to
    1259         * copy information about the properties' structure (i.e. the
    1260         * parent-child-relationships will get lost). So when dealing with
    1261         * hierarchical configuration objects their
    1262         * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods
    1263         * should be used.
    1264         *
    1265         * @param c the configuration to be appended (can be <b>null</b>, then this
    1266         * operation will have no effect)
    1267         * @since 1.5
    1268         */
    1269        public void append(Configuration c)
    1270        {
    1271            if (c != null)
    1272            {
    1273                for (Iterator it = c.getKeys(); it.hasNext();)
    1274                {
    1275                    String key = (String) it.next();
    1276                    Object value = c.getProperty(key);
    1277                    fireEvent(EVENT_ADD_PROPERTY, key, value, true);
    1278                    addPropertyValues(key, value, DISABLED_DELIMITER);
    1279                    fireEvent(EVENT_ADD_PROPERTY, key, value, false);
    1280                }
    1281            }
    1282        }
    1283
    1284        /**
    1285         * Returns a configuration with the same content as this configuration, but
    1286         * with all variables replaced by their actual values. This method tries to
    1287         * clone the configuration and then perform interpolation on all properties.
    1288         * So property values of the form <code>${var}</code> will be resolved as
    1289         * far as possible (if a variable cannot be resolved, it remains unchanged).
    1290         * This operation is useful if the content of a configuration is to be
    1291         * exported or processed by an external component that does not support
    1292         * variable interpolation.
    1293         *
    1294         * @return a configuration with all variables interpolated
    1295         * @throws ConfigurationRuntimeException if this configuration cannot be
    1296         * cloned
    1297         * @since 1.5
    1298         */
    1299        public Configuration interpolatedConfiguration()
    1300        {
    1301            // first clone this configuration
    1302            AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils
    1303                    .cloneConfiguration(this);
    1304
    1305            // now perform interpolation
    1306            c.setDelimiterParsingDisabled(true);
    1307            for (Iterator it = getKeys(); it.hasNext();)
    1308            {
    1309                String key = (String) it.next();
    1310                c.setProperty(key, getList(key));
    1311            }
    1312
    1313            c.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
    1314            return c;
    1315        }
    1316    }