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
018package org.apache.commons.configuration;
019
020import java.lang.reflect.Array;
021import java.math.BigDecimal;
022import java.math.BigInteger;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029import java.util.NoSuchElementException;
030import java.util.Properties;
031
032import org.apache.commons.configuration.event.ConfigurationErrorEvent;
033import org.apache.commons.configuration.event.ConfigurationErrorListener;
034import org.apache.commons.configuration.event.EventSource;
035import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
036import org.apache.commons.lang.BooleanUtils;
037import org.apache.commons.lang.ClassUtils;
038import org.apache.commons.lang.ObjectUtils;
039import org.apache.commons.lang.text.StrLookup;
040import org.apache.commons.lang.text.StrSubstitutor;
041import org.apache.commons.logging.Log;
042import 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 1534064 2013-10-21 08:44:33Z henning $
080 */
081public 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<?> 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 = (List<Object>) 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}