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