001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration;
019
020import java.awt.Color;
021import java.io.Serializable;
022import java.lang.reflect.Array;
023import java.math.BigDecimal;
024import java.math.BigInteger;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.Calendar;
028import java.util.Collection;
029import java.util.Date;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Locale;
033import java.util.NoSuchElementException;
034
035import org.apache.commons.lang.ClassUtils;
036import 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 */
089public 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}