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