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