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