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