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