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