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