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    }