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.lang.reflect.Array;
022    import java.lang.reflect.Constructor;
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Method;
025    import java.math.BigDecimal;
026    import java.math.BigInteger;
027    import java.net.InetAddress;
028    import java.net.MalformedURLException;
029    import java.net.URL;
030    import java.net.UnknownHostException;
031    import java.text.ParseException;
032    import java.text.SimpleDateFormat;
033    import java.util.ArrayList;
034    import java.util.Calendar;
035    import java.util.Collection;
036    import java.util.Date;
037    import java.util.Iterator;
038    import java.util.List;
039    import java.util.Locale;
040    
041    import org.apache.commons.collections.IteratorUtils;
042    import org.apache.commons.collections.iterators.IteratorChain;
043    import org.apache.commons.collections.iterators.SingletonIterator;
044    import org.apache.commons.lang.BooleanUtils;
045    import org.apache.commons.lang.StringUtils;
046    import org.apache.commons.lang.SystemUtils;
047    
048    /**
049     * A utility class to convert the configuration properties into any type.
050     *
051     * @author Emmanuel Bourg
052     * @version $Revision: 1034102 $, $Date: 2010-11-11 21:28:05 +0100 (Do, 11. Nov 2010) $
053     * @since 1.1
054     */
055    public final class PropertyConverter
056    {
057        /** Constant for the list delimiter as char.*/
058        static final char LIST_ESC_CHAR = '\\';
059    
060        /** Constant for the list delimiter escaping character as string.*/
061        static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR);
062    
063        /** Constant for the prefix of hex numbers.*/
064        private static final String HEX_PREFIX = "0x";
065    
066        /** Constant for the radix of hex numbers.*/
067        private static final int HEX_RADIX = 16;
068    
069        /** Constant for the Java version 1.5.*/
070        private static final float JAVA_VERSION_1_5 = 1.5f;
071    
072        /** Constant for the argument classes of the Number constructor that takes a String. */
073        private static final Class[] CONSTR_ARGS = {String.class};
074    
075        /** The fully qualified name of {@link javax.mail.internet.InternetAddress} */
076        private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress";
077    
078        /**
079         * Private constructor prevents instances from being created.
080         */
081        private PropertyConverter()
082        {
083            // to prevent instanciation...
084        }
085    
086        /**
087         * Converts the specified value to the target class. If the class is a
088         * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned
089         * will use the wrapper type (Integer.class, Boolean.class, etc).
090         *
091         * @param cls   the target class of the converted value
092         * @param value the value to convert
093         * @param params optional parameters used for the conversion
094         * @return the converted value
095         * @throws ConversionException if the value is not compatible with the requested type
096         *
097         * @since 1.5
098         */
099        static Object to(Class cls, Object value, Object[] params) throws ConversionException
100        {
101            if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls))
102            {
103                return toBoolean(value);
104            }
105            else if (Number.class.isAssignableFrom(cls) || cls.isPrimitive())
106            {
107                if (Integer.class.equals(cls) || Integer.TYPE.equals(cls))
108                {
109                    return toInteger(value);
110                }
111                else if (Long.class.equals(cls) || Long.TYPE.equals(cls))
112                {
113                    return toLong(value);
114                }
115                else if (Byte.class.equals(cls) || Byte.TYPE.equals(cls))
116                {
117                    return toByte(value);
118                }
119                else if (Short.class.equals(cls) || Short.TYPE.equals(cls))
120                {
121                    return toShort(value);
122                }
123                else if (Float.class.equals(cls) || Float.TYPE.equals(cls))
124                {
125                    return toFloat(value);
126                }
127                else if (Double.class.equals(cls) || Double.TYPE.equals(cls))
128                {
129                    return toDouble(value);
130                }
131                else if (BigInteger.class.equals(cls))
132                {
133                    return toBigInteger(value);
134                }
135                else if (BigDecimal.class.equals(cls))
136                {
137                    return toBigDecimal(value);
138                }
139            }
140            else if (Date.class.equals(cls))
141            {
142                return toDate(value, (String) params[0]);
143            }
144            else if (Calendar.class.equals(cls))
145            {
146                return toCalendar(value, (String) params[0]);
147            }
148            else if (URL.class.equals(cls))
149            {
150                return toURL(value);
151            }
152            else if (Locale.class.equals(cls))
153            {
154                return toLocale(value);
155            }
156            else if (isEnum(cls))
157            {
158                return toEnum(value, cls);
159            }
160            else if (Color.class.equals(cls))
161            {
162                return toColor(value);
163            }
164            else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME))
165            {
166                return toInternetAddress(value);
167            }
168            else if (InetAddress.class.isAssignableFrom(cls))
169            {
170                return toInetAddress(value);
171            }
172    
173            throw new ConversionException("The value '" + value + "' (" + value.getClass() + ")"
174                    + " can't be converted to a " + cls.getName() + " object");
175        }
176    
177        /**
178         * Convert the specified object into a Boolean. Internally the
179         * <code>org.apache.commons.lang.BooleanUtils</code> class from the
180         * <a href="http://commons.apache.org/lang/">Commons Lang</a>
181         * project is used to perform this conversion. This class accepts some more
182         * tokens for the boolean value of <b>true</b>, e.g. <code>yes</code> and
183         * <code>on</code>. Please refer to the documentation of this class for more
184         * details.
185         *
186         * @param value the value to convert
187         * @return the converted value
188         * @throws ConversionException thrown if the value cannot be converted to a boolean
189         */
190        public static Boolean toBoolean(Object value) throws ConversionException
191        {
192            if (value instanceof Boolean)
193            {
194                return (Boolean) value;
195            }
196            else if (value instanceof String)
197            {
198                Boolean b = BooleanUtils.toBooleanObject((String) value);
199                if (b == null)
200                {
201                    throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
202                }
203                return b;
204            }
205            else
206            {
207                throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
208            }
209        }
210    
211        /**
212         * Convert the specified object into a Byte.
213         *
214         * @param value the value to convert
215         * @return the converted value
216         * @throws ConversionException thrown if the value cannot be converted to a byte
217         */
218        public static Byte toByte(Object value) throws ConversionException
219        {
220            Number n = toNumber(value, Byte.class);
221            if (n instanceof Byte)
222            {
223                return (Byte) n;
224            }
225            else
226            {
227                return new Byte(n.byteValue());
228            }
229        }
230    
231        /**
232         * Convert the specified object into a Short.
233         *
234         * @param value the value to convert
235         * @return the converted value
236         * @throws ConversionException thrown if the value cannot be converted to a short
237         */
238        public static Short toShort(Object value) throws ConversionException
239        {
240            Number n = toNumber(value, Short.class);
241            if (n instanceof Short)
242            {
243                return (Short) n;
244            }
245            else
246            {
247                return new Short(n.shortValue());
248            }
249        }
250    
251        /**
252         * Convert the specified object into an Integer.
253         *
254         * @param value the value to convert
255         * @return the converted value
256         * @throws ConversionException thrown if the value cannot be converted to an integer
257         */
258        public static Integer toInteger(Object value) throws ConversionException
259        {
260            Number n = toNumber(value, Integer.class);
261            if (n instanceof Integer)
262            {
263                return (Integer) n;
264            }
265            else
266            {
267                return new Integer(n.intValue());
268            }
269        }
270    
271        /**
272         * Convert the specified object into a Long.
273         *
274         * @param value the value to convert
275         * @return the converted value
276         * @throws ConversionException thrown if the value cannot be converted to a Long
277         */
278        public static Long toLong(Object value) throws ConversionException
279        {
280            Number n = toNumber(value, Long.class);
281            if (n instanceof Long)
282            {
283                return (Long) n;
284            }
285            else
286            {
287                return new Long(n.longValue());
288            }
289        }
290    
291        /**
292         * Convert the specified object into a Float.
293         *
294         * @param value the value to convert
295         * @return the converted value
296         * @throws ConversionException thrown if the value cannot be converted to a Float
297         */
298        public static Float toFloat(Object value) throws ConversionException
299        {
300            Number n = toNumber(value, Float.class);
301            if (n instanceof Float)
302            {
303                return (Float) n;
304            }
305            else
306            {
307                return new Float(n.floatValue());
308            }
309        }
310    
311        /**
312         * Convert the specified object into a Double.
313         *
314         * @param value the value to convert
315         * @return the converted value
316         * @throws ConversionException thrown if the value cannot be converted to a Double
317         */
318        public static Double toDouble(Object value) throws ConversionException
319        {
320            Number n = toNumber(value, Double.class);
321            if (n instanceof Double)
322            {
323                return (Double) n;
324            }
325            else
326            {
327                return new Double(n.doubleValue());
328            }
329        }
330    
331        /**
332         * Convert the specified object into a BigInteger.
333         *
334         * @param value the value to convert
335         * @return the converted value
336         * @throws ConversionException thrown if the value cannot be converted to a BigInteger
337         */
338        public static BigInteger toBigInteger(Object value) throws ConversionException
339        {
340            Number n = toNumber(value, BigInteger.class);
341            if (n instanceof BigInteger)
342            {
343                return (BigInteger) n;
344            }
345            else
346            {
347                return BigInteger.valueOf(n.longValue());
348            }
349        }
350    
351        /**
352         * Convert the specified object into a BigDecimal.
353         *
354         * @param value the value to convert
355         * @return the converted value
356         * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
357         */
358        public static BigDecimal toBigDecimal(Object value) throws ConversionException
359        {
360            Number n = toNumber(value, BigDecimal.class);
361            if (n instanceof BigDecimal)
362            {
363                return (BigDecimal) n;
364            }
365            else
366            {
367                return new BigDecimal(n.doubleValue());
368            }
369        }
370    
371        /**
372         * Tries to convert the specified object into a number object. This method
373         * is used by the conversion methods for number types. Note that the return
374         * value is not in always of the specified target class, but only if a new
375         * object has to be created.
376         *
377         * @param value the value to be converted (must not be <b>null</b>)
378         * @param targetClass the target class of the conversion (must be derived
379         * from <code>java.lang.Number</code>)
380         * @return the converted number
381         * @throws ConversionException if the object cannot be converted
382         */
383        static Number toNumber(Object value, Class targetClass) throws ConversionException
384        {
385            if (value instanceof Number)
386            {
387                return (Number) value;
388            }
389            else
390            {
391                String str = value.toString();
392                if (str.startsWith(HEX_PREFIX))
393                {
394                    try
395                    {
396                        return new BigInteger(str.substring(HEX_PREFIX.length()), HEX_RADIX);
397                    }
398                    catch (NumberFormatException nex)
399                    {
400                        throw new ConversionException("Could not convert " + str
401                                + " to " + targetClass.getName()
402                                + "! Invalid hex number.", nex);
403                    }
404                }
405    
406                try
407                {
408                    Constructor constr = targetClass.getConstructor(CONSTR_ARGS);
409                    return (Number) constr.newInstance(new Object[]{str});
410                }
411                catch (InvocationTargetException itex)
412                {
413                    throw new ConversionException("Could not convert " + str
414                            + " to " + targetClass.getName(), itex
415                            .getTargetException());
416                }
417                catch (Exception ex)
418                {
419                    // Treat all possible exceptions the same way
420                    throw new ConversionException(
421                            "Conversion error when trying to convert " + str
422                                    + " to " + targetClass.getName(), ex);
423                }
424            }
425        }
426    
427        /**
428         * Convert the specified object into an URL.
429         *
430         * @param value the value to convert
431         * @return the converted value
432         * @throws ConversionException thrown if the value cannot be converted to an URL
433         */
434        public static URL toURL(Object value) throws ConversionException
435        {
436            if (value instanceof URL)
437            {
438                return (URL) value;
439            }
440            else if (value instanceof String)
441            {
442                try
443                {
444                    return new URL((String) value);
445                }
446                catch (MalformedURLException e)
447                {
448                    throw new ConversionException("The value " + value + " can't be converted to an URL", e);
449                }
450            }
451            else
452            {
453                throw new ConversionException("The value " + value + " can't be converted to an URL");
454            }
455        }
456    
457        /**
458         * Convert the specified object into a Locale.
459         *
460         * @param value the value to convert
461         * @return the converted value
462         * @throws ConversionException thrown if the value cannot be converted to a Locale
463         */
464        public static Locale toLocale(Object value) throws ConversionException
465        {
466            if (value instanceof Locale)
467            {
468                return (Locale) value;
469            }
470            else if (value instanceof String)
471            {
472                List elements = split((String) value, '_');
473                int size = elements.size();
474    
475                if (size >= 1 && (((String) elements.get(0)).length() == 2 || ((String) elements.get(0)).length() == 0))
476                {
477                    String language = (String) elements.get(0);
478                    String country = (String) ((size >= 2) ? elements.get(1) : "");
479                    String variant = (String) ((size >= 3) ? elements.get(2) : "");
480    
481                    return new Locale(language, country, variant);
482                }
483                else
484                {
485                    throw new ConversionException("The value " + value + " can't be converted to a Locale");
486                }
487            }
488            else
489            {
490                throw new ConversionException("The value " + value + " can't be converted to a Locale");
491            }
492        }
493    
494        /**
495         * Split a string on the specified delimiter. To be removed when
496         * commons-lang has a better replacement available (Tokenizer?).
497         *
498         * todo: replace with a commons-lang equivalent
499         *
500         * @param s          the string to split
501         * @param delimiter  the delimiter
502         * @param trim       a flag whether the single elements should be trimmed
503         * @return a list with the single tokens
504         */
505        public static List split(String s, char delimiter, boolean trim)
506        {
507            if (s == null)
508            {
509                return new ArrayList();
510            }
511    
512            List list = new ArrayList();
513    
514            StringBuffer token = new StringBuffer();
515            int begin = 0;
516            boolean inEscape = false;
517    
518            while (begin < s.length())
519            {
520                char c = s.charAt(begin);
521                if (inEscape)
522                {
523                    // last character was the escape marker
524                    // can current character be escaped?
525                    if (c != delimiter && c != LIST_ESC_CHAR)
526                    {
527                        // no, also add escape character
528                        token.append(LIST_ESC_CHAR);
529                    }
530                    token.append(c);
531                    inEscape = false;
532                }
533    
534                else
535                {
536                    if (c == delimiter)
537                    {
538                        // found a list delimiter -> add token and resetDefaultFileSystem buffer
539                        String t = token.toString();
540                        if (trim)
541                        {
542                            t = t.trim();
543                        }
544                        list.add(t);
545                        token = new StringBuffer();
546                    }
547                    else if (c == LIST_ESC_CHAR)
548                    {
549                        // eventually escape next character
550                        inEscape = true;
551                    }
552                    else
553                    {
554                        token.append(c);
555                    }
556                }
557    
558                begin++;
559            }
560    
561            // Trailing delimiter?
562            if (inEscape)
563            {
564                token.append(LIST_ESC_CHAR);
565            }
566            // Add last token
567            String t = token.toString();
568            if (trim)
569            {
570                t = t.trim();
571            }
572            list.add(t);
573    
574            return list;
575        }
576    
577        /**
578         * Split a string on the specified delimiter always trimming the elements.
579         * This is a shortcut for <code>split(s, delimiter, true)</code>.
580         *
581         * @param s          the string to split
582         * @param delimiter  the delimiter
583         * @return a list with the single tokens
584         */
585        public static List split(String s, char delimiter)
586        {
587            return split(s, delimiter, true);
588        }
589    
590        /**
591         * Escapes the delimiters that might be contained in the given string. This
592         * method works like {@link #escapeListDelimiter(String, char)}. In addition,
593         * a single backslash will also be escaped.
594         *
595         * @param s the string with the value
596         * @param delimiter the list delimiter to use
597         * @return the correctly escaped string
598         */
599        public static String escapeDelimiters(String s, char delimiter)
600        {
601            String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE);
602            return escapeListDelimiter(s1, delimiter);
603        }
604    
605        /**
606         * Escapes the list delimiter if it is contained in the given string. This
607         * method ensures that list delimiter characters that are part of a
608         * property's value are correctly escaped when a configuration is saved to a
609         * file. Otherwise when loaded again the property will be treated as a list
610         * property.
611         *
612         * @param s the string with the value
613         * @param delimiter the list delimiter to use
614         * @return the escaped string
615         * @since 1.7
616         */
617        public static String escapeListDelimiter(String s, char delimiter)
618        {
619            return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE
620                    + delimiter);
621        }
622    
623        /**
624         * Convert the specified object into a Color. If the value is a String,
625         * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
626         * <ul>
627         *   <li>FF0000 (red)</li>
628         *   <li>0000FFA0 (semi transparent blue)</li>
629         *   <li>#CCCCCC (gray)</li>
630         *   <li>#00FF00A0 (semi transparent green)</li>
631         * </ul>
632         *
633         * @param value the value to convert
634         * @return the converted value
635         * @throws ConversionException thrown if the value cannot be converted to a Color
636         */
637        public static Color toColor(Object value) throws ConversionException
638        {
639            if (value instanceof Color)
640            {
641                return (Color) value;
642            }
643            else if (value instanceof String && !StringUtils.isBlank((String) value))
644            {
645                String color = ((String) value).trim();
646    
647                int[] components = new int[3];
648    
649                // check the size of the string
650                int minlength = components.length * 2;
651                if (color.length() < minlength)
652                {
653                    throw new ConversionException("The value " + value + " can't be converted to a Color");
654                }
655    
656                // remove the leading #
657                if (color.startsWith("#"))
658                {
659                    color = color.substring(1);
660                }
661    
662                try
663                {
664                    // parse the components
665                    for (int i = 0; i < components.length; i++)
666                    {
667                        components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
668                    }
669    
670                    // parse the transparency
671                    int alpha;
672                    if (color.length() >= minlength + 2)
673                    {
674                        alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
675                    }
676                    else
677                    {
678                        alpha = Color.black.getAlpha();
679                    }
680    
681                    return new Color(components[0], components[1], components[2], alpha);
682                }
683                catch (Exception e)
684                {
685                    throw new ConversionException("The value " + value + " can't be converted to a Color", e);
686                }
687            }
688            else
689            {
690                throw new ConversionException("The value " + value + " can't be converted to a Color");
691            }
692        }
693    
694        /**
695         * Convert the specified value into an internet address.
696         *
697         * @param value the value to convert
698         * @return the converted value
699         * @throws ConversionException thrown if the value cannot be converted to a InetAddress
700         *
701         * @since 1.5
702         */
703        static InetAddress toInetAddress(Object value) throws ConversionException
704        {
705            if (value instanceof InetAddress)
706            {
707                return (InetAddress) value;
708            }
709            else if (value instanceof String)
710            {
711                try
712                {
713                    return InetAddress.getByName((String) value);
714                }
715                catch (UnknownHostException e)
716                {
717                    throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e);
718                }
719            }
720            else
721            {
722                throw new ConversionException("The value " + value + " can't be converted to a InetAddress");
723            }
724        }
725    
726        /**
727         * Convert the specified value into an email address.
728         *
729         * @param value the value to convert
730         * @return the converted value
731         * @throws ConversionException thrown if the value cannot be converted to an email address
732         *
733         * @since 1.5
734         */
735        static Object toInternetAddress(Object value) throws ConversionException
736        {
737            if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME))
738            {
739                return value;
740            }
741            else if (value instanceof String)
742            {
743                try
744                {
745                    Constructor ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME).getConstructor(new Class[] {String.class});
746                    return ctor.newInstance(new Object[] {value});
747                }
748                catch (Exception e)
749                {
750                    throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e);
751                }
752            }
753            else
754            {
755                throw new ConversionException("The value " + value + " can't be converted to a InternetAddress");
756            }
757        }
758    
759        /**
760         * Calls Class.isEnum() on Java 5, returns false on older JRE.
761         */
762        static boolean isEnum(Class cls)
763        {
764            if (!SystemUtils.isJavaVersionAtLeast(JAVA_VERSION_1_5))
765            {
766                return false;
767            }
768    
769            try
770            {
771                Method isEnumMethod = Class.class.getMethod("isEnum", new Class[] {});
772                return ((Boolean) isEnumMethod.invoke(cls, new Object[] {})).booleanValue();
773            }
774            catch (Exception e)
775            {
776                // impossible
777                throw new RuntimeException(e.getMessage());
778            }
779        }
780    
781        /**
782         * Convert the specified value into a Java 5 enum.
783         *
784         * @param value the value to convert
785         * @param cls   the type of the enumeration
786         * @return the converted value
787         * @throws ConversionException thrown if the value cannot be converted to an enumeration
788         *
789         * @since 1.5
790         */
791        static Object toEnum(Object value, Class cls) throws ConversionException
792        {
793            if (value.getClass().equals(cls))
794            {
795                return value;
796            }
797            else if (value instanceof String)
798            {
799                try
800                {
801                    Method valueOfMethod = cls.getMethod("valueOf", new Class[] {String.class});
802                    return valueOfMethod.invoke(null, new Object[] {value});
803                }
804                catch (Exception e)
805                {
806                    throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
807                }
808            }
809            else if (value instanceof Number)
810            {
811                try
812                {
813                    Method valuesMethod = cls.getMethod("values", new Class[] {});
814                    Object valuesArray = valuesMethod.invoke(null, new Object[] {});
815    
816                    return Array.get(valuesArray, ((Number) value).intValue());
817                }
818                catch (Exception e)
819                {
820                    throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
821                }
822            }
823            else
824            {
825                throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
826            }
827        }
828    
829        /**
830         * Convert the specified object into a Date.
831         *
832         * @param value  the value to convert
833         * @param format the DateFormat pattern to parse String values
834         * @return the converted value
835         * @throws ConversionException thrown if the value cannot be converted to a Calendar
836         */
837        public static Date toDate(Object value, String format) throws ConversionException
838        {
839            if (value instanceof Date)
840            {
841                return (Date) value;
842            }
843            else if (value instanceof Calendar)
844            {
845                return ((Calendar) value).getTime();
846            }
847            else if (value instanceof String)
848            {
849                try
850                {
851                    return new SimpleDateFormat(format).parse((String) value);
852                }
853                catch (ParseException e)
854                {
855                    throw new ConversionException("The value " + value + " can't be converted to a Date", e);
856                }
857            }
858            else
859            {
860                throw new ConversionException("The value " + value + " can't be converted to a Date");
861            }
862        }
863    
864        /**
865         * Convert the specified object into a Calendar.
866         *
867         * @param value  the value to convert
868         * @param format the DateFormat pattern to parse String values
869         * @return the converted value
870         * @throws ConversionException thrown if the value cannot be converted to a Calendar
871         */
872        public static Calendar toCalendar(Object value, String format) throws ConversionException
873        {
874            if (value instanceof Calendar)
875            {
876                return (Calendar) value;
877            }
878            else if (value instanceof Date)
879            {
880                Calendar calendar = Calendar.getInstance();
881                calendar.setTime((Date) value);
882                return calendar;
883            }
884            else if (value instanceof String)
885            {
886                try
887                {
888                    Calendar calendar = Calendar.getInstance();
889                    calendar.setTime(new SimpleDateFormat(format).parse((String) value));
890                    return calendar;
891                }
892                catch (ParseException e)
893                {
894                    throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
895                }
896            }
897            else
898            {
899                throw new ConversionException("The value " + value + " can't be converted to a Calendar");
900            }
901        }
902    
903        /**
904         * Return an iterator over the simple values of a composite value. The value
905         * specified is handled depending on its type:
906         * <ul>
907         *   <li>Strings are checked for delimiter characters and splitted if necessary.</li>
908         *   <li>For collections the single elements are checked.</li>
909         *   <li>Arrays are treated like collections.</li>
910         *   <li>All other types are directly inserted.</li>
911         *   <li>Recursive combinations are supported, e.g. a collection containing array that contain strings.</li>
912         * </ul>
913         *
914         * @param value     the value to "split"
915         * @param delimiter the delimiter for String values
916         * @return an iterator for accessing the single values
917         */
918        public static Iterator toIterator(Object value, char delimiter)
919        {
920            if (value == null)
921            {
922                return IteratorUtils.emptyIterator();
923            }
924            if (value instanceof String)
925            {
926                String s = (String) value;
927                if (s.indexOf(delimiter) > 0)
928                {
929                    return split((String) value, delimiter).iterator();
930                }
931                else
932                {
933                    return new SingletonIterator(value);
934                }
935            }
936            else if (value instanceof Collection)
937            {
938                return toIterator(((Collection) value).iterator(), delimiter);
939            }
940            else if (value.getClass().isArray())
941            {
942                return toIterator(IteratorUtils.arrayIterator(value), delimiter);
943            }
944            else if (value instanceof Iterator)
945            {
946                Iterator iterator = (Iterator) value;
947                IteratorChain chain = new IteratorChain();
948                while (iterator.hasNext())
949                {
950                    chain.addIterator(toIterator(iterator.next(), delimiter));
951                }
952                return chain;
953            }
954            else
955            {
956                return new SingletonIterator(value);
957            }
958        }
959    
960        /**
961         * Performs interpolation of the specified value. This method checks if the
962         * given value contains variables of the form <code>${...}</code>. If
963         * this is the case, all occurrances will be substituted by their current
964         * values.
965         *
966         * @param value the value to be interpolated
967         * @param config the current configuration object
968         * @return the interpolated value
969         */
970        public static Object interpolate(Object value, AbstractConfiguration config)
971        {
972            if (value instanceof String)
973            {
974                return config.getSubstitutor().replace((String) value);
975            }
976            else
977            {
978                return value;
979            }
980        }
981    }