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.awt.Color;
021    import java.io.Serializable;
022    import java.lang.reflect.Array;
023    import java.math.BigDecimal;
024    import java.math.BigInteger;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.Calendar;
028    import java.util.Collection;
029    import java.util.Date;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Locale;
033    import java.util.NoSuchElementException;
034    
035    import org.apache.commons.lang.ClassUtils;
036    import org.apache.commons.lang.StringUtils;
037    
038    /**
039     * Decorator providing additional getters for any Configuration. This extended
040     * Configuration supports more types:
041     * <ul>
042     *   <li>{@link java.net.URL}</li>
043     *   <li>{@link java.util.Locale}</li>
044     *   <li>{@link java.util.Date}</li>
045     *   <li>{@link java.util.Calendar}</li>
046     *   <li>{@link java.awt.Color}</li>
047     *   <li>{@link java.net.InetAddress}</li>
048     *   <li>{@link javax.mail.internet.InternetAddress} (requires Javamail in the classpath)</li>
049     *   <li>{@link java.lang.Enum} (Java 5 enumeration types)</li>
050     * </ul>
051     *
052     * Lists and arrays are available for all types.
053     *
054     * <h4>Example</h4>
055     *
056     * Configuration file <tt>config.properties</tt>:
057     * <pre>
058     * title.color = #0000FF
059     * remote.host = 192.168.0.53
060     * default.locales = fr,en,de
061     * email.contact = ebourg@apache.org, oheger@apache.org
062     * </pre>
063     *
064     * Usage:
065     *
066     * <pre>
067     * DataConfiguration config = new DataConfiguration(new PropertiesConfiguration("config.properties"));
068     *
069     * // retrieve a property using a specialized getter
070     * Color color = config.getColor("title.color");
071     *
072     * // retrieve a property using a generic getter
073     * InetAddress host = (InetAddress) config.get(InetAddress.class, "remote.host");
074     * Locale[] locales = (Locale[]) config.getArray(Locale.class, "default.locales");
075     * List contacts = config.getList(InternetAddress.class, "email.contact");
076     * </pre>
077     *
078     * <h4>Dates</h4>
079     *
080     * Date objects are expected to be formatted with the pattern <tt>yyyy-MM-dd HH:mm:ss</tt>.
081     * This default format can be changed by specifying another format in the
082     * getters, or by putting a date format in the configuration under the key
083     * <tt>org.apache.commons.configuration.format.date</tt>.
084     *
085     * @author <a href="ebourg@apache.org">Emmanuel Bourg</a>
086     * @version $Id: DataConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
087     * @since 1.1
088     */
089    public class DataConfiguration extends AbstractConfiguration implements Serializable
090    {
091        /** The key of the property storing the user defined date format. */
092        public static final String DATE_FORMAT_KEY = "org.apache.commons.configuration.format.date";
093    
094        /** The default format for dates. */
095        public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
096    
097        /**
098         * The serial version UID.
099         */
100        private static final long serialVersionUID = -69011336405718640L;
101    
102        /** Stores the wrapped configuration.*/
103        protected Configuration configuration;
104    
105        /**
106         * Creates a new instance of {@code DataConfiguration} and sets the
107         * wrapped configuration.
108         *
109         * @param configuration the wrapped configuration
110         */
111        public DataConfiguration(Configuration configuration)
112        {
113            this.configuration = configuration;
114        }
115    
116        /**
117         * Return the configuration decorated by this DataConfiguration.
118         *
119         * @return the wrapped configuration
120         */
121        public Configuration getConfiguration()
122        {
123            return configuration;
124        }
125    
126        public Object getProperty(String key)
127        {
128            return configuration.getProperty(key);
129        }
130    
131        @Override
132        protected void addPropertyDirect(String key, Object obj)
133        {
134            if (configuration instanceof AbstractConfiguration)
135            {
136                ((AbstractConfiguration) configuration).addPropertyDirect(key, obj);
137            }
138            else
139            {
140                configuration.addProperty(key, obj);
141            }
142        }
143    
144        @Override
145        public void addProperty(String key, Object value)
146        {
147            getConfiguration().addProperty(key, value);
148        }
149    
150        public boolean isEmpty()
151        {
152            return configuration.isEmpty();
153        }
154    
155        public boolean containsKey(String key)
156        {
157            return configuration.containsKey(key);
158        }
159    
160        @Override
161        public void clearProperty(String key)
162        {
163            configuration.clearProperty(key);
164        }
165    
166        @Override
167        public void setProperty(String key, Object value)
168        {
169            configuration.setProperty(key, value);
170        }
171    
172        public Iterator<String> getKeys()
173        {
174            return configuration.getKeys();
175        }
176    
177        /**
178         * Get an object of the specified type associated with the given
179         * configuration key. If the key doesn't map to an existing object, the
180         * method returns null unless {@link #isThrowExceptionOnMissing()} is set
181         * to <tt>true</tt>.
182         *
183         * @param <T> the target type of the value
184         * @param cls the target class of the value
185         * @param key the key of the value
186         *
187         * @return the value of the requested type for the key
188         *
189         * @throws NoSuchElementException if the key doesn't map to an existing
190         *     object and <tt>throwExceptionOnMissing=true</tt>
191         * @throws ConversionException if the value is not compatible with the requested type
192         *
193         * @since 1.5
194         */
195        public <T> T get(Class<T> cls, String key)
196        {
197            T value = get(cls, key, null);
198            if (value != null)
199            {
200                return value;
201            }
202            else if (isThrowExceptionOnMissing())
203            {
204                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
205            }
206            else
207            {
208                return null;
209            }
210        }
211    
212        /**
213         * Get an object of the specified type associated with the given
214         * configuration key. If the key doesn't map to an existing object, the
215         * default value is returned.
216         *
217         * @param <T>          the target type of the value
218         * @param cls          the target class of the value
219         * @param key          the key of the value
220         * @param defaultValue the default value
221         *
222         * @return the value of the requested type for the key
223         *
224         * @throws ConversionException if the value is not compatible with the requested type
225         *
226         * @since 1.5
227         */
228        public <T> T get(Class<T> cls, String key, T defaultValue)
229        {
230            Object value = resolveContainerStore(key);
231    
232            if (value == null)
233            {
234                return defaultValue;
235            }
236    
237            if (Date.class.equals(cls) || Calendar.class.equals(cls))
238            {
239                return convert(cls, key, interpolate(value), new String[] {getDefaultDateFormat()});
240            }
241            else
242            {
243                return convert(cls, key, interpolate(value), null);
244            }
245        }
246    
247        /**
248         * Get a list of typed objects associated with the given configuration key.
249         * If the key doesn't map to an existing object, an empty list is returned.
250         *
251         * @param <T> the type expected for the elements of the list
252         * @param cls the class expected for the elements of the list
253         * @param key The configuration key.
254         * @return The associated list if the key is found.
255         *
256         * @throws ConversionException is thrown if the key maps to an object that
257         *     is not compatible with a list of the specified class.
258         *
259         * @since 1.5
260         */
261        public <T> List<T> getList(Class<T> cls, String key)
262        {
263            return getList(cls, key, new ArrayList<T>());
264        }
265    
266        /**
267         * Get a list of typed objects associated with the given configuration key.
268         * If the key doesn't map to an existing object, the default value is
269         * returned.
270         *
271         * @param <T>          the type expected for the elements of the list
272         * @param cls          the class expected for the elements of the list
273         * @param key          the configuration key.
274         * @param defaultValue the default value.
275         * @return The associated List.
276         *
277         * @throws ConversionException is thrown if the key maps to an object that
278         *     is not compatible with a list of the specified class.
279         *
280         * @since 1.5
281         */
282        public <T> List<T> getList(Class<T> cls, String key, List<T> defaultValue)
283        {
284            Object value = getProperty(key);
285            Class<?> valueClass = value != null ? value.getClass() : null;
286    
287            List<T> list;
288    
289            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
290            {
291                // the value is null or is an empty string
292                list = defaultValue;
293            }
294            else
295            {
296                list = new ArrayList<T>();
297    
298                Object[] params = null;
299                if (cls.equals(Date.class) || cls.equals(Calendar.class))
300                {
301                    params = new Object[] {getDefaultDateFormat()};
302                }
303    
304                if (valueClass.isArray())
305                {
306                    // get the class of the objects contained in the array
307                    Class<?> arrayType = valueClass.getComponentType();
308                    int length = Array.getLength(value);
309    
310                    if (arrayType.equals(cls)
311                            || (arrayType.isPrimitive() && cls.equals(ClassUtils.primitiveToWrapper(arrayType))))
312                    {
313                        // the value is an array of the specified type, or an array
314                        // of the primitive type derived from the specified type
315                        for (int i = 0; i < length; i++)
316                        {
317                            list.add(cls.cast(Array.get(value, i)));
318                        }
319                    }
320                    else
321                    {
322                        // attempt to convert the elements of the array
323                        for (int i = 0; i < length; i++)
324                        {
325                            list.add(convert(cls, key, interpolate(Array.get(value, i)), params));
326                        }
327                    }
328                }
329                else if (value instanceof Collection)
330                {
331                    Collection<?> values = (Collection<?>) value;
332    
333                    for (Object o : values)
334                    {
335                        list.add(convert(cls, key, interpolate(o), params));
336                    }
337                }
338                else
339                {
340                    // attempt to convert a single value
341                    list.add(convert(cls, key, interpolate(value), params));
342                }
343            }
344    
345            return list;
346        }
347    
348        /**
349         * Get an array of typed objects associated with the given configuration key.
350         * If the key doesn't map to an existing object, an empty list is returned.
351         *
352         * @param cls the type expected for the elements of the array
353         * @param key The configuration key.
354         * @return The associated array if the key is found, and the value compatible with the type specified.
355         *
356         * @throws ConversionException is thrown if the key maps to an object that
357         *     is not compatible with a list of the specified class.
358         *
359         * @since 1.5
360         */
361        public Object getArray(Class<?> cls, String key)
362        {
363            return getArray(cls, key, Array.newInstance(cls, 0));
364        }
365    
366        /**
367         * Get an array of typed objects associated with the given configuration key.
368         * If the key doesn't map to an existing object, the default value is returned.
369         *
370         * @param cls          the type expected for the elements of the array
371         * @param key          the configuration key.
372         * @param defaultValue the default value
373         * @return The associated array if the key is found, and the value compatible with the type specified.
374         *
375         * @throws ConversionException is thrown if the key maps to an object that
376         *     is not compatible with an array of the specified class.
377         * @throws IllegalArgumentException if the default value is not an array of the specified type
378         *
379         * @since 1.5
380         */
381        public Object getArray(Class<?> cls, String key, Object defaultValue)
382        {
383            // check the type of the default value
384            if (defaultValue != null
385                    && (!defaultValue.getClass().isArray() || !cls
386                            .isAssignableFrom(defaultValue.getClass()
387                                    .getComponentType())))
388            {
389                throw new IllegalArgumentException(
390                        "The type of the default value (" + defaultValue.getClass()
391                                + ")" + " is not an array of the specified class ("
392                                + cls + ")");
393            }
394    
395            if (cls.isPrimitive())
396            {
397                return getPrimitiveArray(cls, key, defaultValue);
398            }
399    
400            List<?> list = getList(cls, key);
401            if (list.isEmpty())
402            {
403                return defaultValue;
404            }
405            else
406            {
407                return list.toArray((Object[]) Array.newInstance(cls, list.size()));
408            }
409        }
410    
411        /**
412         * Get an array of primitive values associated with the given configuration key.
413         * If the key doesn't map to an existing object, the default value is returned.
414         *
415         * @param cls          the primitive type expected for the elements of the array
416         * @param key          the configuration key.
417         * @param defaultValue the default value
418         * @return The associated array if the key is found, and the value compatible with the type specified.
419         *
420         * @throws ConversionException is thrown if the key maps to an object that
421         *     is not compatible with an array of the specified class.
422         *
423         * @since 1.5
424         */
425        private Object getPrimitiveArray(Class<?> cls, String key, Object defaultValue)
426        {
427            Object value = getProperty(key);
428            Class<?> valueClass = value != null ? value.getClass() : null;
429    
430            Object array;
431    
432            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
433            {
434                // the value is null or is an empty string
435                array = defaultValue;
436            }
437            else
438            {
439                if (valueClass.isArray())
440                {
441                    // get the class of the objects contained in the array
442                    Class<?> arrayType = valueClass.getComponentType();
443                    int length = Array.getLength(value);
444    
445                    if (arrayType.equals(cls))
446                    {
447                        // the value is an array of the same primitive type
448                        array = value;
449                    }
450                    else if (arrayType.equals(ClassUtils.primitiveToWrapper(cls)))
451                    {
452                        // the value is an array of the wrapper type derived from the specified primitive type
453                        array = Array.newInstance(cls, length);
454    
455                        for (int i = 0; i < length; i++)
456                        {
457                            Array.set(array, i, Array.get(value, i));
458                        }
459                    }
460                    else
461                    {
462                        throw new ConversionException('\'' + key + "' (" + arrayType + ")"
463                                + " doesn't map to a compatible array of " + cls);
464                    }
465                }
466                else if (value instanceof Collection)
467                {
468                    Collection<?> values = (Collection<?>) value;
469    
470                    array = Array.newInstance(cls, values.size());
471    
472                    int i = 0;
473                    for (Object o : values)
474                    {
475                        // This is safe because PropertyConverter can handle
476                        // conversion to wrapper classes correctly.
477                        @SuppressWarnings("unchecked")
478                        Object convertedValue = convert(ClassUtils.primitiveToWrapper(cls), key, interpolate(o), null);
479                        Array.set(array, i++, convertedValue);
480                    }
481                }
482                else
483                {
484                    // attempt to convert a single value
485                    // This is safe because PropertyConverter can handle
486                    // conversion to wrapper classes correctly.
487                    @SuppressWarnings("unchecked")
488                    Object convertedValue = convert(ClassUtils.primitiveToWrapper(cls), key, interpolate(value), null);
489    
490                    // create an array of one element
491                    array = Array.newInstance(cls, 1);
492                    Array.set(array, 0, convertedValue);
493                }
494            }
495    
496            return array;
497        }
498    
499        /**
500         * Get a list of Boolean objects associated with the given
501         * configuration key. If the key doesn't map to an existing object
502         * an empty list is returned.
503         *
504         * @param key The configuration key.
505         * @return The associated Boolean list if the key is found.
506         *
507         * @throws ConversionException is thrown if the key maps to an
508         *         object that is not a list of booleans.
509         */
510        public List<Boolean> getBooleanList(String key)
511        {
512            return getBooleanList(key, new ArrayList<Boolean>());
513        }
514    
515        /**
516         * Get a list of Boolean objects associated with the given
517         * configuration key. If the key doesn't map to an existing object,
518         * the default value is returned.
519         *
520         * @param key The configuration key.
521         * @param defaultValue The default value.
522         * @return The associated List of Booleans.
523         *
524         * @throws ConversionException is thrown if the key maps to an
525         *         object that is not a list of booleans.
526         */
527        public List<Boolean> getBooleanList(String key, List<Boolean> defaultValue)
528        {
529             return getList(Boolean.class, key, defaultValue);
530        }
531    
532        /**
533         * Get an array of boolean primitives associated with the given
534         * configuration key. If the key doesn't map to an existing object
535         * an empty array is returned.
536         *
537         * @param key The configuration key.
538         * @return The associated boolean array if the key is found.
539         *
540         * @throws ConversionException is thrown if the key maps to an
541         *         object that is not a list of booleans.
542         */
543        public boolean[] getBooleanArray(String key)
544        {
545            return (boolean[]) getArray(Boolean.TYPE, key);
546        }
547    
548        /**
549         * Get an array of boolean primitives associated with the given
550         * configuration key. If the key doesn't map to an existing object,
551         * the default value is returned.
552         *
553         * @param key          The configuration key.
554         * @param defaultValue The default value.
555         * @return The associated boolean array if the key is found.
556         *
557         * @throws ConversionException is thrown if the key maps to an
558         *         object that is not a list of booleans.
559         */
560        public boolean[] getBooleanArray(String key, boolean[] defaultValue)
561        {
562            return (boolean[]) getArray(Boolean.TYPE, key, defaultValue);
563        }
564    
565        /**
566         * Get a list of Byte objects associated with the given configuration key.
567         * If the key doesn't map to an existing object an empty list is returned.
568         *
569         * @param key The configuration key.
570         * @return The associated Byte list if the key is found.
571         *
572         * @throws ConversionException is thrown if the key maps to an
573         *         object that is not a list of bytes.
574         */
575        public List<Byte> getByteList(String key)
576        {
577            return getByteList(key, new ArrayList<Byte>());
578        }
579    
580        /**
581         * Get a list of Byte objects associated with the given configuration key.
582         * If the key doesn't map to an existing object, the default value is
583         * returned.
584         *
585         * @param key The configuration key.
586         * @param defaultValue The default value.
587         * @return The associated List of Bytes.
588         *
589         * @throws ConversionException is thrown if the key maps to an
590         *         object that is not a list of bytes.
591         */
592        public List<Byte> getByteList(String key, List<Byte> defaultValue)
593        {
594            return getList(Byte.class, key, defaultValue);
595        }
596    
597        /**
598         * Get an array of byte primitives associated with the given
599         * configuration key. If the key doesn't map to an existing object
600         * an empty array is returned.
601         *
602         * @param key The configuration key.
603         * @return The associated byte array if the key is found.
604         *
605         * @throws ConversionException is thrown if the key maps to an
606         *         object that is not a list of bytes.
607         */
608        public byte[] getByteArray(String key)
609        {
610            return getByteArray(key, new byte[0]);
611        }
612    
613        /**
614         * Get an array of byte primitives associated with the given
615         * configuration key. If the key doesn't map to an existing object
616         * an empty array is returned.
617         *
618         * @param key The configuration key.
619         * @param defaultValue the default value, which will be returned if the property is not found
620         * @return The associated byte array if the key is found.
621         *
622         * @throws ConversionException is thrown if the key maps to an
623         *         object that is not a list of bytes.
624         */
625        public byte[] getByteArray(String key, byte[] defaultValue)
626        {
627            return (byte[]) getArray(Byte.TYPE, key, defaultValue);
628        }
629    
630        /**
631         * Get a list of Short objects associated with the given configuration key.
632         * If the key doesn't map to an existing object an empty list is returned.
633         *
634         * @param key The configuration key.
635         * @return The associated Short list if the key is found.
636         *
637         * @throws ConversionException is thrown if the key maps to an
638         *         object that is not a list of shorts.
639         */
640        public List<Short> getShortList(String key)
641        {
642            return getShortList(key, new ArrayList<Short>());
643        }
644    
645        /**
646         * Get a list of Short objects associated with the given configuration key.
647         * If the key doesn't map to an existing object, the default value is
648         * returned.
649         *
650         * @param key The configuration key.
651         * @param defaultValue The default value.
652         * @return The associated List of Shorts.
653         *
654         * @throws ConversionException is thrown if the key maps to an
655         *         object that is not a list of shorts.
656         */
657        public List<Short> getShortList(String key, List<Short> defaultValue)
658        {
659            return getList(Short.class, key, defaultValue);
660        }
661    
662        /**
663         * Get an array of short primitives associated with the given
664         * configuration key. If the key doesn't map to an existing object
665         * an empty array is returned.
666         *
667         * @param key The configuration key.
668         * @return The associated short array if the key is found.
669         *
670         * @throws ConversionException is thrown if the key maps to an
671         *         object that is not a list of shorts.
672         */
673        public short[] getShortArray(String key)
674        {
675            return getShortArray(key, new short[0]);
676        }
677    
678        /**
679         * Get an array of short primitives associated with the given
680         * configuration key. If the key doesn't map to an existing object
681         * an empty array is returned.
682         *
683         * @param key The configuration key.
684         * @param defaultValue the default value, which will be returned if the property is not found
685         * @return The associated short array if the key is found.
686         *
687         * @throws ConversionException is thrown if the key maps to an
688         *         object that is not a list of shorts.
689         */
690        public short[] getShortArray(String key, short[] defaultValue)
691        {
692            return (short[]) getArray(Short.TYPE, key, defaultValue);
693        }
694    
695        /**
696         * Get a list of Integer objects associated with the given
697         * configuration key. If the key doesn't map to an existing object
698         * an empty list is returned.
699         *
700         * @param key The configuration key.
701         * @return The associated Integer list if the key is found.
702         *
703         * @throws ConversionException is thrown if the key maps to an
704         *         object that is not a list of integers.
705         */
706        public List<Integer> getIntegerList(String key)
707        {
708            return getIntegerList(key, new ArrayList<Integer>());
709        }
710    
711        /**
712         * Get a list of Integer objects associated with the given
713         * configuration key. If the key doesn't map to an existing object,
714         * the default value is returned.
715         *
716         * @param key The configuration key.
717         * @param defaultValue The default value.
718         * @return The associated List of Integers.
719         *
720         * @throws ConversionException is thrown if the key maps to an
721         *         object that is not a list of integers.
722         */
723        public List<Integer> getIntegerList(String key, List<Integer> defaultValue)
724        {
725            return getList(Integer.class, key, defaultValue);
726        }
727    
728        /**
729         * Get an array of int primitives associated with the given
730         * configuration key. If the key doesn't map to an existing object
731         * an empty array is returned.
732         *
733         * @param key The configuration key.
734         * @return The associated int array if the key is found.
735         *
736         * @throws ConversionException is thrown if the key maps to an
737         *         object that is not a list of integers.
738         */
739        public int[] getIntArray(String key)
740        {
741            return getIntArray(key, new int[0]);
742        }
743    
744        /**
745         * Get an array of int primitives associated with the given
746         * configuration key. If the key doesn't map to an existing object
747         * an empty array is returned.
748         *
749         * @param key The configuration key.
750         * @param defaultValue the default value, which will be returned if the property is not found
751         * @return The associated int array if the key is found.
752         *
753         * @throws ConversionException is thrown if the key maps to an
754         *         object that is not a list of integers.
755         */
756        public int[] getIntArray(String key, int[] defaultValue)
757        {
758            return (int[]) getArray(Integer.TYPE, key, defaultValue);
759        }
760    
761        /**
762         * Get a list of Long objects associated with the given configuration key.
763         * If the key doesn't map to an existing object an empty list is returned.
764         *
765         * @param key The configuration key.
766         * @return The associated Long list if the key is found.
767         *
768         * @throws ConversionException is thrown if the key maps to an
769         *         object that is not a list of longs.
770         */
771        public List<Long> getLongList(String key)
772        {
773            return getLongList(key, new ArrayList<Long>());
774        }
775    
776        /**
777         * Get a list of Long objects associated with the given configuration key.
778         * If the key doesn't map to an existing object, the default value is
779         * returned.
780         *
781         * @param key The configuration key.
782         * @param defaultValue The default value.
783         * @return The associated List of Longs.
784         *
785         * @throws ConversionException is thrown if the key maps to an
786         *         object that is not a list of longs.
787         */
788        public List<Long> getLongList(String key, List<Long> defaultValue)
789        {
790            return getList(Long.class, key, defaultValue);
791        }
792    
793        /**
794         * Get an array of long primitives associated with the given
795         * configuration key. If the key doesn't map to an existing object
796         * an empty array is returned.
797         *
798         * @param key The configuration key.
799         * @return The associated long array if the key is found.
800         *
801         * @throws ConversionException is thrown if the key maps to an
802         *         object that is not a list of longs.
803         */
804        public long[] getLongArray(String key)
805        {
806            return getLongArray(key, new long[0]);
807        }
808    
809        /**
810         * Get an array of long primitives associated with the given
811         * configuration key. If the key doesn't map to an existing object
812         * an empty array is returned.
813         *
814         * @param key The configuration key.
815         * @param defaultValue the default value, which will be returned if the property is not found
816         * @return The associated long array if the key is found.
817         *
818         * @throws ConversionException is thrown if the key maps to an
819         *         object that is not a list of longs.
820         */
821        public long[] getLongArray(String key, long[] defaultValue)
822        {
823            return (long[]) getArray(Long.TYPE, key, defaultValue);
824        }
825    
826        /**
827         * Get a list of Float objects associated with the given configuration key.
828         * If the key doesn't map to an existing object an empty list is returned.
829         *
830         * @param key The configuration key.
831         * @return The associated Float list if the key is found.
832         *
833         * @throws ConversionException is thrown if the key maps to an
834         *         object that is not a list of floats.
835         */
836        public List<Float> getFloatList(String key)
837        {
838            return getFloatList(key, new ArrayList<Float>());
839        }
840    
841        /**
842         * Get a list of Float objects associated with the given
843         * configuration key. If the key doesn't map to an existing object,
844         * the default value is returned.
845         *
846         * @param key The configuration key.
847         * @param defaultValue The default value.
848         * @return The associated List of Floats.
849         *
850         * @throws ConversionException is thrown if the key maps to an
851         *         object that is not a list of floats.
852         */
853        public List<Float> getFloatList(String key, List<Float> defaultValue)
854        {
855            return getList(Float.class, key, defaultValue);
856        }
857    
858        /**
859         * Get an array of float primitives associated with the given
860         * configuration key. If the key doesn't map to an existing object
861         * an empty array is returned.
862         *
863         * @param key The configuration key.
864         * @return The associated float array if the key is found.
865         *
866         * @throws ConversionException is thrown if the key maps to an
867         *         object that is not a list of floats.
868         */
869        public float[] getFloatArray(String key)
870        {
871            return getFloatArray(key, new float[0]);
872        }
873    
874        /**
875         * Get an array of float primitives associated with the given
876         * configuration key. If the key doesn't map to an existing object
877         * an empty array is returned.
878         *
879         * @param key The configuration key.
880         * @param defaultValue the default value, which will be returned if the property is not found
881         * @return The associated float array if the key is found.
882         *
883         * @throws ConversionException is thrown if the key maps to an
884         *         object that is not a list of floats.
885         */
886        public float[] getFloatArray(String key, float[] defaultValue)
887        {
888            return (float[]) getArray(Float.TYPE, key, defaultValue);
889        }
890    
891        /**
892         * Get a list of Double objects associated with the given
893         * configuration key. If the key doesn't map to an existing object
894         * an empty list is returned.
895         *
896         * @param key The configuration key.
897         * @return The associated Double list if the key is found.
898         *
899         * @throws ConversionException is thrown if the key maps to an
900         *         object that is not a list of doubles.
901         */
902        public List<Double> getDoubleList(String key)
903        {
904            return getDoubleList(key, new ArrayList<Double>());
905        }
906    
907        /**
908         * Get a list of Double objects associated with the given
909         * configuration key. If the key doesn't map to an existing object,
910         * the default value is returned.
911         *
912         * @param key The configuration key.
913         * @param defaultValue The default value.
914         * @return The associated List of Doubles.
915         *
916         * @throws ConversionException is thrown if the key maps to an
917         *         object that is not a list of doubles.
918         */
919        public List<Double> getDoubleList(String key, List<Double> defaultValue)
920        {
921            return getList(Double.class, key, defaultValue);
922        }
923    
924        /**
925         * Get an array of double primitives associated with the given
926         * configuration key. If the key doesn't map to an existing object
927         * an empty array is returned.
928         *
929         * @param key The configuration key.
930         * @return The associated double array if the key is found.
931         *
932         * @throws ConversionException is thrown if the key maps to an
933         *         object that is not a list of doubles.
934         */
935        public double[] getDoubleArray(String key)
936        {
937            return getDoubleArray(key, new double[0]);
938        }
939    
940        /**
941         * Get an array of double primitives associated with the given
942         * configuration key. If the key doesn't map to an existing object
943         * an empty array is returned.
944         *
945         * @param key The configuration key.
946         * @param defaultValue the default value, which will be returned if the property is not found
947         * @return The associated double array if the key is found.
948         *
949         * @throws ConversionException is thrown if the key maps to an
950         *         object that is not a list of doubles.
951         */
952        public double[] getDoubleArray(String key, double[] defaultValue)
953        {
954            return (double[]) getArray(Double.TYPE, key, defaultValue);
955        }
956    
957        /**
958         * Get a list of BigIntegers associated with the given configuration key.
959         * If the key doesn't map to an existing object an empty list is returned.
960         *
961         * @param key The configuration key.
962         * @return The associated BigInteger list if the key is found.
963         *
964         * @throws ConversionException is thrown if the key maps to an
965         *         object that is not a list of BigIntegers.
966         */
967        public List<BigInteger> getBigIntegerList(String key)
968        {
969            return getBigIntegerList(key, new ArrayList<BigInteger>());
970        }
971    
972        /**
973         * Get a list of BigIntegers associated with the given configuration key.
974         * If the key doesn't map to an existing object, the default value is
975         * returned.
976         *
977         * @param key The configuration key.
978         * @param defaultValue The default value.
979         * @return The associated List of BigIntegers.
980         *
981         * @throws ConversionException is thrown if the key maps to an
982         *         object that is not a list of BigIntegers.
983         */
984        public List<BigInteger> getBigIntegerList(String key, List<BigInteger> defaultValue)
985        {
986            return getList(BigInteger.class, key, defaultValue);
987        }
988    
989        /**
990         * Get an array of BigIntegers associated with the given
991         * configuration key. If the key doesn't map to an existing object
992         * an empty array is returned.
993         *
994         * @param key The configuration key.
995         * @return The associated BigInteger array if the key is found.
996         *
997         * @throws ConversionException is thrown if the key maps to an
998         *         object that is not a list of BigIntegers.
999         */
1000        public BigInteger[] getBigIntegerArray(String key)
1001        {
1002            return getBigIntegerArray(key, new BigInteger[0]);
1003        }
1004    
1005        /**
1006         * Get an array of BigIntegers associated with the given
1007         * configuration key. If the key doesn't map to an existing object
1008         * an empty array is returned.
1009         *
1010         * @param key The configuration key.
1011         * @param defaultValue the default value, which will be returned if the property is not found
1012         * @return The associated BigInteger array if the key is found.
1013         *
1014         * @throws ConversionException is thrown if the key maps to an
1015         *         object that is not a list of BigIntegers.
1016         */
1017        public BigInteger[] getBigIntegerArray(String key, BigInteger[] defaultValue)
1018        {
1019            return (BigInteger[]) getArray(BigInteger.class, key, defaultValue);
1020        }
1021    
1022        /**
1023         * Get a list of BigDecimals associated with the given configuration key.
1024         * If the key doesn't map to an existing object an empty list is returned.
1025         *
1026         * @param key The configuration key.
1027         * @return The associated BigDecimal list if the key is found.
1028         *
1029         * @throws ConversionException is thrown if the key maps to an
1030         *         object that is not a list of BigDecimals.
1031         */
1032        public List<BigDecimal> getBigDecimalList(String key)
1033        {
1034            return getBigDecimalList(key, new ArrayList<BigDecimal>());
1035        }
1036    
1037        /**
1038         * Get a list of BigDecimals associated with the given configuration key.
1039         * If the key doesn't map to an existing object, the default value is
1040         * returned.
1041         *
1042         * @param key The configuration key.
1043         * @param defaultValue The default value.
1044         * @return The associated List of BigDecimals.
1045         *
1046         * @throws ConversionException is thrown if the key maps to an
1047         *         object that is not a list of BigDecimals.
1048         */
1049        public List<BigDecimal> getBigDecimalList(String key, List<BigDecimal> defaultValue)
1050        {
1051            return getList(BigDecimal.class, key, defaultValue);
1052        }
1053    
1054        /**
1055         * Get an array of BigDecimals associated with the given
1056         * configuration key. If the key doesn't map to an existing object
1057         * an empty array is returned.
1058         *
1059         * @param key The configuration key.
1060         * @return The associated BigDecimal array if the key is found.
1061         *
1062         * @throws ConversionException is thrown if the key maps to an
1063         *         object that is not a list of BigDecimals.
1064         */
1065        public BigDecimal[] getBigDecimalArray(String key)
1066        {
1067            return getBigDecimalArray(key, new BigDecimal[0]);
1068        }
1069    
1070        /**
1071         * Get an array of BigDecimals associated with the given
1072         * configuration key. If the key doesn't map to an existing object
1073         * an empty array is returned.
1074         *
1075         * @param key The configuration key.
1076         * @param defaultValue the default value, which will be returned if the property is not found
1077         * @return The associated BigDecimal array if the key is found.
1078         *
1079         * @throws ConversionException is thrown if the key maps to an
1080         *         object that is not a list of BigDecimals.
1081         */
1082        public BigDecimal[] getBigDecimalArray(String key, BigDecimal[] defaultValue)
1083        {
1084            return (BigDecimal[]) getArray(BigDecimal.class, key, defaultValue);
1085        }
1086    
1087        /**
1088         * Get an URL associated with the given configuration key.
1089         *
1090         * @param key The configuration key.
1091         * @return The associated URL.
1092         *
1093         * @throws ConversionException is thrown if the key maps to an
1094         *         object that is not an URL.
1095         */
1096        public URL getURL(String key)
1097        {
1098            return get(URL.class, key);
1099        }
1100    
1101        /**
1102         * Get an URL associated with the given configuration key.
1103         * If the key doesn't map to an existing object, the default value
1104         * is returned.
1105         *
1106         * @param key          The configuration key.
1107         * @param defaultValue The default value.
1108         * @return The associated URL.
1109         *
1110         * @throws ConversionException is thrown if the key maps to an
1111         *         object that is not an URL.
1112         */
1113        public URL getURL(String key, URL defaultValue)
1114        {
1115            return get(URL.class, key, defaultValue);
1116        }
1117    
1118        /**
1119         * Get a list of URLs associated with the given configuration key.
1120         * If the key doesn't map to an existing object an empty list is returned.
1121         *
1122         * @param key The configuration key.
1123         * @return The associated URL list if the key is found.
1124         *
1125         * @throws ConversionException is thrown if the key maps to an
1126         *         object that is not a list of URLs.
1127         */
1128        public List<URL> getURLList(String key)
1129        {
1130            return getURLList(key, new ArrayList<URL>());
1131        }
1132    
1133        /**
1134         * Get a list of URLs associated with the given configuration key.
1135         * If the key doesn't map to an existing object, the default value is
1136         * returned.
1137         *
1138         * @param key The configuration key.
1139         * @param defaultValue The default value.
1140         * @return The associated List of URLs.
1141         *
1142         * @throws ConversionException is thrown if the key maps to an
1143         *         object that is not a list of URLs.
1144         */
1145        public List<URL> getURLList(String key, List<URL> defaultValue)
1146        {
1147            return getList(URL.class, key, defaultValue);
1148        }
1149    
1150        /**
1151         * Get an array of URLs associated with the given configuration key.
1152         * If the key doesn't map to an existing object an empty array is returned.
1153         *
1154         * @param key The configuration key.
1155         * @return The associated URL array if the key is found.
1156         *
1157         * @throws ConversionException is thrown if the key maps to an
1158         *         object that is not a list of URLs.
1159         */
1160        public URL[] getURLArray(String key)
1161        {
1162            return getURLArray(key, new URL[0]);
1163        }
1164    
1165        /**
1166         * Get an array of URLs associated with the given configuration key.
1167         * If the key doesn't map to an existing object an empty array is returned.
1168         *
1169         * @param key The configuration key.
1170         * @param defaultValue the default value, which will be returned if the property is not found
1171         * @return The associated URL array if the key is found.
1172         *
1173         * @throws ConversionException is thrown if the key maps to an
1174         *         object that is not a list of URLs.
1175         */
1176        public URL[] getURLArray(String key, URL[] defaultValue)
1177        {
1178            return (URL[]) getArray(URL.class, key, defaultValue);
1179        }
1180    
1181        /**
1182         * Get a Date associated with the given configuration key. If the property
1183         * is a String, it will be parsed with the format defined by the user in
1184         * the {@link #DATE_FORMAT_KEY} property, or if it's not defined with the
1185         * {@link #DEFAULT_DATE_FORMAT} pattern.
1186         *
1187         * @param key The configuration key.
1188         * @return The associated Date.
1189         *
1190         * @throws ConversionException is thrown if the key maps to an
1191         *         object that is not a Date.
1192         */
1193        public Date getDate(String key)
1194        {
1195            return get(Date.class, key);
1196        }
1197    
1198        /**
1199         * Get a Date associated with the given configuration key. If the property
1200         * is a String, it will be parsed with the specified format pattern.
1201         *
1202         * @param key    The configuration key.
1203         * @param format The non-localized {@link java.text.DateFormat} pattern.
1204         * @return The associated Date
1205         *
1206         * @throws ConversionException is thrown if the key maps to an
1207         *         object that is not a Date.
1208         */
1209        public Date getDate(String key, String format)
1210        {
1211            Date value = getDate(key, null, format);
1212            if (value != null)
1213            {
1214                return value;
1215            }
1216            else if (isThrowExceptionOnMissing())
1217            {
1218                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1219            }
1220            else
1221            {
1222                return null;
1223            }
1224        }
1225    
1226        /**
1227         * Get a Date associated with the given configuration key. If the property
1228         * is a String, it will be parsed with the format defined by the user in
1229         * the {@link #DATE_FORMAT_KEY} property, or if it's not defined with the
1230         * {@link #DEFAULT_DATE_FORMAT} pattern. If the key doesn't map to an
1231         * existing object, the default value is returned.
1232         *
1233         * @param key          The configuration key.
1234         * @param defaultValue The default value.
1235         * @return The associated Date.
1236         *
1237         * @throws ConversionException is thrown if the key maps to an
1238         *         object that is not a Date.
1239         */
1240        public Date getDate(String key, Date defaultValue)
1241        {
1242            return getDate(key, defaultValue, getDefaultDateFormat());
1243        }
1244    
1245        /**
1246         * Get a Date associated with the given configuration key. If the property
1247         * is a String, it will be parsed with the specified format pattern.
1248         * If the key doesn't map to an existing object, the default value
1249         * is returned.
1250         *
1251         * @param key          The configuration key.
1252         * @param defaultValue The default value.
1253         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1254         * @return The associated Date.
1255         *
1256         * @throws ConversionException is thrown if the key maps to an
1257         *         object that is not a Date.
1258         */
1259        public Date getDate(String key, Date defaultValue, String format)
1260        {
1261            Object value = resolveContainerStore(key);
1262    
1263            if (value == null)
1264            {
1265                return defaultValue;
1266            }
1267            else
1268            {
1269                try
1270                {
1271                    return PropertyConverter.toDate(interpolate(value), format);
1272                }
1273                catch (ConversionException e)
1274                {
1275                    throw new ConversionException('\'' + key + "' doesn't map to a Date", e);
1276                }
1277            }
1278        }
1279        public List<Date> getDateList(String key)
1280        {
1281            return getDateList(key, new ArrayList<Date>());
1282        }
1283    
1284        /**
1285         * Get a list of Dates associated with the given configuration key.
1286         * If the property is a list of Strings, they will be parsed with the
1287         * specified format pattern. If the key doesn't map to an existing object
1288         * an empty list is returned.
1289         *
1290         * @param key    The configuration key.
1291         * @param format The non-localized {@link java.text.DateFormat} pattern.
1292         * @return The associated Date list if the key is found.
1293         *
1294         * @throws ConversionException is thrown if the key maps to an
1295         *         object that is not a list of Dates.
1296         */
1297        public List<Date> getDateList(String key, String format)
1298        {
1299            return getDateList(key, new ArrayList<Date>(), format);
1300        }
1301    
1302        /**
1303         * Get a list of Dates associated with the given configuration key.
1304         * If the property is a list of Strings, they will be parsed with the
1305         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1306         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1307         * If the key doesn't map to an existing object, the default value is
1308         * returned.
1309         *
1310         * @param key          The configuration key.
1311         * @param defaultValue The default value.
1312         * @return The associated Date list if the key is found.
1313         *
1314         * @throws ConversionException is thrown if the key maps to an
1315         *         object that is not a list of Dates.
1316         */
1317        public List<Date> getDateList(String key, List<Date> defaultValue)
1318        {
1319            return getDateList(key, defaultValue, getDefaultDateFormat());
1320        }
1321    
1322        /**
1323         * Get a list of Dates associated with the given configuration key.
1324         * If the property is a list of Strings, they will be parsed with the
1325         * specified format pattern. If the key doesn't map to an existing object,
1326         * the default value is returned.
1327         *
1328         * @param key          The configuration key.
1329         * @param defaultValue The default value.
1330         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1331         * @return The associated Date list if the key is found.
1332         *
1333         * @throws ConversionException is thrown if the key maps to an
1334         *         object that is not a list of Dates.
1335         */
1336        public List<Date> getDateList(String key, List<Date> defaultValue, String format)
1337        {
1338            Object value = getProperty(key);
1339    
1340            List<Date> list;
1341    
1342            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
1343            {
1344                list = defaultValue;
1345            }
1346            else if (value.getClass().isArray())
1347            {
1348                list = new ArrayList<Date>();
1349                int length = Array.getLength(value);
1350                for (int i = 0; i < length; i++)
1351                {
1352                    list.add(convert(Date.class, key, interpolate(Array.get(value, i)), new String[] {format}));
1353                }
1354            }
1355            else if (value instanceof Collection)
1356            {
1357                Collection<?> values = (Collection<?>) value;
1358                list = new ArrayList<Date>();
1359    
1360                for (Object o : values)
1361                {
1362                    list.add(convert(Date.class, key, interpolate(o), new String[] {format}));
1363                }
1364            }
1365            else
1366            {
1367                // attempt to convert a single value
1368                list = new ArrayList<Date>();
1369                list.add(convert(Date.class, key, interpolate(value), new String[] {format}));
1370            }
1371    
1372            return list;
1373        }
1374    
1375        /**
1376         * Get an array of Dates associated with the given configuration key.
1377         * If the property is a list of Strings, they will be parsed with the
1378         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1379         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1380         * If the key doesn't map to an existing object an empty array is returned.
1381         *
1382         * @param key The configuration key.
1383         * @return The associated Date array if the key is found.
1384         *
1385         * @throws ConversionException is thrown if the key maps to an
1386         *         object that is not a list of Dates.
1387         */
1388        public Date[] getDateArray(String key)
1389        {
1390            return getDateArray(key, new Date[0]);
1391        }
1392    
1393        /**
1394         * Get an array of Dates associated with the given configuration key.
1395         * If the property is a list of Strings, they will be parsed with the
1396         * specified format pattern. If the key doesn't map to an existing object
1397         * an empty array is returned.
1398         *
1399         * @param key    The configuration key.
1400         * @param format The non-localized {@link java.text.DateFormat} pattern.
1401         * @return The associated Date array if the key is found.
1402         *
1403         * @throws ConversionException is thrown if the key maps to an
1404         *         object that is not a list of Dates.
1405         */
1406        public Date[] getDateArray(String key, String format)
1407        {
1408            return getDateArray(key, new Date[0], format);
1409        }
1410    
1411        /**
1412         * Get an array of Dates associated with the given configuration key.
1413         * If the property is a list of Strings, they will be parsed with the
1414         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1415         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1416         * If the key doesn't map to an existing object an empty array is returned.
1417         *
1418         * @param key The configuration key.
1419         * @param defaultValue the default value, which will be returned if the property is not found
1420         * @return The associated Date array if the key is found.
1421         *
1422         * @throws ConversionException is thrown if the key maps to an
1423         *         object that is not a list of Dates.
1424         */
1425        public Date[] getDateArray(String key, Date[] defaultValue)
1426        {
1427            return getDateArray(key, defaultValue, getDefaultDateFormat());
1428        }
1429    
1430        /**
1431         * Get an array of Dates associated with the given configuration key.
1432         * If the property is a list of Strings, they will be parsed with the
1433         * specified format pattern. If the key doesn't map to an existing object,
1434         * the default value is returned.
1435         *
1436         * @param key          The configuration key.
1437         * @param defaultValue The default value.
1438         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1439         * @return The associated Date array if the key is found.
1440         *
1441         * @throws ConversionException is thrown if the key maps to an
1442         *         object that is not a list of Dates.
1443         */
1444        public Date[] getDateArray(String key, Date[] defaultValue, String format)
1445        {
1446            List<Date> list = getDateList(key, format);
1447            if (list.isEmpty())
1448            {
1449                return defaultValue;
1450            }
1451            else
1452            {
1453                return list.toArray(new Date[list.size()]);
1454            }
1455        }
1456    
1457        /**
1458         * Get a Calendar associated with the given configuration key. If the
1459         * property is a String, it will be parsed with the format defined by the
1460         * user in the {@link #DATE_FORMAT_KEY} property, or if it's not defined
1461         * with the {@link #DEFAULT_DATE_FORMAT} pattern.
1462         *
1463         * @param key The configuration key.
1464         * @return The associated Calendar.
1465         *
1466         * @throws ConversionException is thrown if the key maps to an
1467         *         object that is not a Calendar.
1468         */
1469        public Calendar getCalendar(String key)
1470        {
1471            return get(Calendar.class, key);
1472        }
1473    
1474        /**
1475         * Get a Calendar associated with the given configuration key. If the
1476         * property is a String, it will be parsed with the specified format
1477         * pattern.
1478         *
1479         * @param key    The configuration key.
1480         * @param format The non-localized {@link java.text.DateFormat} pattern.
1481         * @return The associated Calendar
1482         *
1483         * @throws ConversionException is thrown if the key maps to an
1484         *         object that is not a Calendar.
1485         */
1486        public Calendar getCalendar(String key, String format)
1487        {
1488            Calendar value = getCalendar(key, null, format);
1489            if (value != null)
1490            {
1491                return value;
1492            }
1493            else if (isThrowExceptionOnMissing())
1494            {
1495                throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1496            }
1497            else
1498            {
1499                return null;
1500            }
1501        }
1502    
1503        /**
1504         * Get a Calendar associated with the given configuration key. If the
1505         * property is a String, it will be parsed with the format defined by the
1506         * user in the {@link #DATE_FORMAT_KEY} property, or if it's not defined
1507         * with the {@link #DEFAULT_DATE_FORMAT} pattern. If the key doesn't map
1508         * to an existing object, the default value is returned.
1509         *
1510         * @param key          The configuration key.
1511         * @param defaultValue The default value.
1512         * @return The associated Calendar.
1513         *
1514         * @throws ConversionException is thrown if the key maps to an
1515         *         object that is not a Calendar.
1516         */
1517        public Calendar getCalendar(String key, Calendar defaultValue)
1518        {
1519            return getCalendar(key, defaultValue, getDefaultDateFormat());
1520        }
1521    
1522        /**
1523         * Get a Calendar associated with the given configuration key. If the
1524         * property is a String, it will be parsed with the specified format
1525         * pattern. If the key doesn't map to an existing object, the default
1526         * value is returned.
1527         *
1528         * @param key          The configuration key.
1529         * @param defaultValue The default value.
1530         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1531         * @return The associated Calendar.
1532         *
1533         * @throws ConversionException is thrown if the key maps to an
1534         *         object that is not a Calendar.
1535         */
1536        public Calendar getCalendar(String key, Calendar defaultValue, String format)
1537        {
1538            Object value = resolveContainerStore(key);
1539    
1540            if (value == null)
1541            {
1542                return defaultValue;
1543            }
1544            else
1545            {
1546                try
1547                {
1548                    return PropertyConverter.toCalendar(interpolate(value), format);
1549                }
1550                catch (ConversionException e)
1551                {
1552                    throw new ConversionException('\'' + key + "' doesn't map to a Calendar", e);
1553                }
1554            }
1555        }
1556    
1557        /**
1558         * Get a list of Calendars associated with the given configuration key.
1559         * If the property is a list of Strings, they will be parsed with the
1560         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1561         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1562         * If the key doesn't map to an existing object an empty list is returned.
1563         *
1564         * @param key The configuration key.
1565         * @return The associated Calendar list if the key is found.
1566         *
1567         * @throws ConversionException is thrown if the key maps to an
1568         *         object that is not a list of Calendars.
1569         */
1570        public List<Calendar> getCalendarList(String key)
1571        {
1572            return getCalendarList(key, new ArrayList<Calendar>());
1573        }
1574    
1575        /**
1576         * Get a list of Calendars associated with the given configuration key.
1577         * If the property is a list of Strings, they will be parsed with the
1578         * specified format pattern. If the key doesn't map to an existing object
1579         * an empty list is returned.
1580         *
1581         * @param key    The configuration key.
1582         * @param format The non-localized {@link java.text.DateFormat} pattern.
1583         * @return The associated Calendar list if the key is found.
1584         *
1585         * @throws ConversionException is thrown if the key maps to an
1586         *         object that is not a list of Calendars.
1587         */
1588        public List<Calendar> getCalendarList(String key, String format)
1589        {
1590            return getCalendarList(key, new ArrayList<Calendar>(), format);
1591        }
1592    
1593        /**
1594         * Get a list of Calendars associated with the given configuration key.
1595         * If the property is a list of Strings, they will be parsed with the
1596         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1597         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1598         * If the key doesn't map to an existing object, the default value is
1599         * returned.
1600         *
1601         * @param key The configuration key.
1602         * @param defaultValue The default value.
1603         * @return The associated Calendar list if the key is found.
1604         *
1605         * @throws ConversionException is thrown if the key maps to an
1606         *         object that is not a list of Calendars.
1607         */
1608        public List<Calendar> getCalendarList(String key, List<Calendar> defaultValue)
1609        {
1610            return getCalendarList(key, defaultValue, getDefaultDateFormat());
1611        }
1612    
1613        /**
1614         * Get a list of Calendars associated with the given configuration key.
1615         * If the property is a list of Strings, they will be parsed with the
1616         * specified format pattern. If the key doesn't map to an existing object,
1617         * the default value is returned.
1618         *
1619         * @param key          The configuration key.
1620         * @param defaultValue The default value.
1621         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1622         * @return The associated Calendar list if the key is found.
1623         *
1624         * @throws ConversionException is thrown if the key maps to an
1625         *         object that is not a list of Calendars.
1626         */
1627        public List<Calendar> getCalendarList(String key, List<Calendar> defaultValue, String format)
1628        {
1629            Object value = getProperty(key);
1630    
1631            List<Calendar> list;
1632    
1633            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
1634            {
1635                list = defaultValue;
1636            }
1637            else if (value.getClass().isArray())
1638            {
1639                list = new ArrayList<Calendar>();
1640                int length = Array.getLength(value);
1641                for (int i = 0; i < length; i++)
1642                {
1643                    list.add(convert(Calendar.class, key, interpolate(Array.get(value, i)), new String[] {format}));
1644                }
1645            }
1646            else if (value instanceof Collection)
1647            {
1648                Collection<?> values = (Collection<?>) value;
1649                list = new ArrayList<Calendar>();
1650    
1651                for (Object o : values)
1652                {
1653                    list.add(convert(Calendar.class, key, interpolate(o), new String[] {format}));
1654                }
1655            }
1656            else
1657            {
1658                // attempt to convert a single value
1659                list = new ArrayList<Calendar>();
1660                list.add(convert(Calendar.class, key, interpolate(value), new String[] {format}));
1661            }
1662    
1663            return list;
1664        }
1665    
1666        /**
1667         * Get an array of Calendars associated with the given configuration key.
1668         * If the property is a list of Strings, they will be parsed with the
1669         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1670         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1671         * If the key doesn't map to an existing object an empty array is returned.
1672         *
1673         * @param key The configuration key.
1674         * @return The associated Calendar array if the key is found.
1675         *
1676         * @throws ConversionException is thrown if the key maps to an
1677         *         object that is not a list of Calendars.
1678         */
1679        public Calendar[] getCalendarArray(String key)
1680        {
1681            return getCalendarArray(key, new Calendar[0]);
1682        }
1683    
1684        /**
1685         * Get an array of Calendars associated with the given configuration key.
1686         * If the property is a list of Strings, they will be parsed with the
1687         * specified format pattern. If the key doesn't map to an existing object
1688         * an empty array is returned.
1689         *
1690         * @param key    The configuration key.
1691         * @param format The non-localized {@link java.text.DateFormat} pattern.
1692         * @return The associated Calendar array if the key is found.
1693         *
1694         * @throws ConversionException is thrown if the key maps to an
1695         *         object that is not a list of Calendars.
1696         */
1697        public Calendar[] getCalendarArray(String key, String format)
1698        {
1699            return getCalendarArray(key, new Calendar[0], format);
1700        }
1701    
1702        /**
1703         * Get an array of Calendars associated with the given configuration key.
1704         * If the property is a list of Strings, they will be parsed with the
1705         * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
1706         * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
1707         * If the key doesn't map to an existing object an empty array is returned.
1708         *
1709         * @param key The configuration key.
1710         * @param defaultValue the default value, which will be returned if the property is not found
1711         * @return The associated Calendar array if the key is found.
1712         *
1713         * @throws ConversionException is thrown if the key maps to an
1714         *         object that is not a list of Calendars.
1715         */
1716        public Calendar[] getCalendarArray(String key, Calendar[] defaultValue)
1717        {
1718            return getCalendarArray(key, defaultValue, getDefaultDateFormat());
1719        }
1720    
1721        /**
1722         * Get an array of Calendars associated with the given configuration key.
1723         * If the property is a list of Strings, they will be parsed with the
1724         * specified format pattern. If the key doesn't map to an existing object,
1725         * the default value is returned.
1726         *
1727         * @param key          The configuration key.
1728         * @param defaultValue The default value.
1729         * @param format       The non-localized {@link java.text.DateFormat} pattern.
1730         * @return The associated Calendar array if the key is found.
1731         *
1732         * @throws ConversionException is thrown if the key maps to an
1733         *         object that is not a list of Calendars.
1734         */
1735        public Calendar[] getCalendarArray(String key, Calendar[] defaultValue, String format)
1736        {
1737            List<Calendar> list = getCalendarList(key, format);
1738            if (list.isEmpty())
1739            {
1740                return defaultValue;
1741            }
1742            else
1743            {
1744                return list.toArray(new Calendar[list.size()]);
1745            }
1746        }
1747    
1748        /**
1749         * Returns the date format specified by the user in the DATE_FORMAT_KEY
1750         * property, or the default format otherwise.
1751         *
1752         * @return the default date format
1753         */
1754        private String getDefaultDateFormat()
1755        {
1756            return getString(DATE_FORMAT_KEY, DEFAULT_DATE_FORMAT);
1757        }
1758    
1759        /**
1760         * Get a Locale associated with the given configuration key.
1761         *
1762         * @param key The configuration key.
1763         * @return The associated Locale.
1764         *
1765         * @throws ConversionException is thrown if the key maps to an
1766         *         object that is not a Locale.
1767         */
1768        public Locale getLocale(String key)
1769        {
1770            return get(Locale.class, key);
1771        }
1772    
1773        /**
1774         * Get a Locale associated with the given configuration key.
1775         * If the key doesn't map to an existing object, the default value
1776         * is returned.
1777         *
1778         * @param key          The configuration key.
1779         * @param defaultValue The default value.
1780         * @return The associated Locale.
1781         *
1782         * @throws ConversionException is thrown if the key maps to an
1783         *         object that is not a Locale.
1784         */
1785        public Locale getLocale(String key, Locale defaultValue)
1786        {
1787            return get(Locale.class, key, defaultValue);
1788        }
1789    
1790        /**
1791         * Get a list of Locales associated with the given configuration key.
1792         * If the key doesn't map to an existing object an empty list is returned.
1793         *
1794         * @param key The configuration key.
1795         * @return The associated Locale list if the key is found.
1796         *
1797         * @throws ConversionException is thrown if the key maps to an
1798         *         object that is not a list of Locales.
1799         */
1800        public List<Locale> getLocaleList(String key)
1801        {
1802            return getLocaleList(key, new ArrayList<Locale>());
1803        }
1804    
1805        /**
1806         * Get a list of Locales associated with the given configuration key.
1807         * If the key doesn't map to an existing object, the default value is
1808         * returned.
1809         *
1810         * @param key The configuration key.
1811         * @param defaultValue The default value.
1812         * @return The associated List of Locales.
1813         *
1814         * @throws ConversionException is thrown if the key maps to an
1815         *         object that is not a list of Locales.
1816         */
1817        public List<Locale> getLocaleList(String key, List<Locale> defaultValue)
1818        {
1819            return getList(Locale.class, key, defaultValue);
1820        }
1821    
1822        /**
1823         * Get an array of Locales associated with the given
1824         * configuration key. If the key doesn't map to an existing object
1825         * an empty array is returned.
1826         *
1827         * @param key The configuration key.
1828         * @return The associated Locale array if the key is found.
1829         *
1830         * @throws ConversionException is thrown if the key maps to an
1831         *         object that is not a list of Locales.
1832         */
1833        public Locale[] getLocaleArray(String key)
1834        {
1835            return getLocaleArray(key, new Locale[0]);
1836        }
1837    
1838        /**
1839         * Get an array of Locales associated with the given
1840         * configuration key. If the key doesn't map to an existing object
1841         * an empty array is returned.
1842         *
1843         * @param key The configuration key.
1844         * @param defaultValue the default value, which will be returned if the property is not found
1845         * @return The associated Locale array if the key is found.
1846         *
1847         * @throws ConversionException is thrown if the key maps to an
1848         *         object that is not a list of Locales.
1849         */
1850        public Locale[] getLocaleArray(String key, Locale[] defaultValue)
1851        {
1852            return (Locale[]) getArray(Locale.class, key, defaultValue);
1853        }
1854    
1855        /**
1856         * Get a Color associated with the given configuration key.
1857         *
1858         * @param key The configuration key.
1859         * @return The associated Color.
1860         *
1861         * @throws ConversionException is thrown if the key maps to an
1862         *         object that is not a Color.
1863         */
1864        public Color getColor(String key)
1865        {
1866            return get(Color.class, key);
1867        }
1868    
1869        /**
1870         * Get a Color associated with the given configuration key.
1871         * If the key doesn't map to an existing object, the default value
1872         * is returned.
1873         *
1874         * @param key          The configuration key.
1875         * @param defaultValue The default value.
1876         * @return The associated Color.
1877         *
1878         * @throws ConversionException is thrown if the key maps to an
1879         *         object that is not a Color.
1880         */
1881        public Color getColor(String key, Color defaultValue)
1882        {
1883            return get(Color.class, key, defaultValue);
1884        }
1885    
1886        /**
1887         * Get a list of Colors associated with the given configuration key.
1888         * If the key doesn't map to an existing object an empty list is returned.
1889         *
1890         * @param key The configuration key.
1891         * @return The associated Color list if the key is found.
1892         *
1893         * @throws ConversionException is thrown if the key maps to an
1894         *         object that is not a list of Colors.
1895         */
1896        public List<Color> getColorList(String key)
1897        {
1898            return getColorList(key, new ArrayList<Color>());
1899        }
1900    
1901        /**
1902         * Get a list of Colors associated with the given configuration key.
1903         * If the key doesn't map to an existing object, the default value is
1904         * returned.
1905         *
1906         * @param key The configuration key.
1907         * @param defaultValue The default value.
1908         * @return The associated List of Colors.
1909         *
1910         * @throws ConversionException is thrown if the key maps to an
1911         *         object that is not a list of Colors.
1912         */
1913        public List<Color> getColorList(String key, List<Color> defaultValue)
1914        {
1915            return getList(Color.class, key, defaultValue);
1916        }
1917    
1918        /**
1919         * Get an array of Colors associated with the given
1920         * configuration key. If the key doesn't map to an existing object
1921         * an empty array is returned.
1922         *
1923         * @param key The configuration key.
1924         * @return The associated Color array if the key is found.
1925         *
1926         * @throws ConversionException is thrown if the key maps to an
1927         *         object that is not a list of Colors.
1928         */
1929        public Color[] getColorArray(String key)
1930        {
1931            return getColorArray(key, new Color[0]);
1932        }
1933    
1934        /**
1935         * Get an array of Colors associated with the given
1936         * configuration key. If the key doesn't map to an existing object
1937         * an empty array is returned.
1938         *
1939         * @param key The configuration key.
1940         * @param defaultValue the default value, which will be returned if the property is not found
1941         * @return The associated Color array if the key is found.
1942         *
1943         * @throws ConversionException is thrown if the key maps to an
1944         *         object that is not a list of Colors.
1945         */
1946        public Color[] getColorArray(String key, Color[] defaultValue)
1947        {
1948            return (Color[]) getArray(Color.class, key, defaultValue);
1949        }
1950    
1951        /**
1952         * Helper method for performing a type conversion using the
1953         * {@code PropertyConverter} class.
1954         *
1955         * @param <T> the target type of the conversion
1956         * @param cls the target class of the conversion
1957         * @param key the configuration key
1958         * @param value the value to be converted
1959         * @param params additional parameters
1960         * @throws ConversionException if the value is not compatible with the
1961         *         requested type
1962         */
1963        private static <T> T convert(Class<T> cls, String key, Object value,
1964                Object[] params)
1965        {
1966            try
1967            {
1968                Object result = PropertyConverter.to(cls, value, params);
1969                // Will not throw a ClassCastException because PropertyConverter
1970                // would have thrown a ConversionException if conversion had failed.
1971                return cls.cast(result);
1972            }
1973            catch (ConversionException e)
1974            {
1975                throw new ConversionException('\'' + key + "' doesn't map to a "
1976                        + cls, e);
1977            }
1978        }
1979    }