    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.configuration2.convert;
    019
    020import java.awt.Color;
    021import java.io.File;
    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.URI;
    029import java.net.URISyntaxException;
    030import java.net.URL;
    031import java.net.UnknownHostException;
    032import java.nio.file.Path;
    033import java.nio.file.Paths;
    034import java.text.ParseException;
    035import java.text.SimpleDateFormat;
    036import java.time.Duration;
    037import java.time.format.DateTimeParseException;
    038import java.util.Calendar;
    039import java.util.Date;
    040import java.util.Locale;
    041import java.util.regex.Pattern;
    042import java.util.regex.PatternSyntaxException;
    043
    044import org.apache.commons.configuration2.ex.ConversionException;
    045import org.apache.commons.lang3.BooleanUtils;
    046import org.apache.commons.lang3.StringUtils;
    047
    048/**
    049 * A utility class to convert the configuration properties into any type.
    050 *
    051 * @since 2.8.0
    052 */
    053public final class PropertyConverter {
    054
    055    /** Constant for the prefix of hex numbers. */
    056    private static final String HEX_PREFIX = "0x";
    057
    058    /** Constant for the radix of hex numbers. */
    059    private static final int HEX_RADIX = 16;
    060
    061    /** Constant for the prefix of binary numbers. */
    062    private static final String BIN_PREFIX = "0b";
    063
    064    /** Constant for the radix of binary numbers. */
    065    private static final int BIN_RADIX = 2;
    066
    067    /** Constant for the argument classes of the Number constructor that takes a String. */
    068    private static final Class<?>[] CONSTR_ARGS = {String.class};
    069
    070    /** The fully qualified name of {@code javax.mail.internet.InternetAddress}, as used in the javamail-1.* API.  */
    071    private static final String INTERNET_ADDRESS_CLASSNAME_JAVAX = "javax.mail.internet.InternetAddress";
    072
    073    /** The fully qualified name of {@code jakarta.mail.internet.InternetAddress}, as used in the javamail-2.0+ API. */
    074    private static final String INTERNET_ADDRESS_CLASSNAME_JAKARTA = "jakarta.mail.internet.InternetAddress";
    075
    076    /**
    077     * Private constructor prevents instances from being created.
    078     */
    079    private PropertyConverter() {
    080        // presvents instantiation.
    081    }
    082
    083    /**
    084     * Converts the specified value object to the given target data class. If additional
    085     * information is required for this conversion, it is obtained from the passed in {@code DefaultConversionHandler}
    086     * object. If the class is a primitive type (Integer.TYPE, Boolean.TYPE, etc), the value returned will use the wrapper
    087     * type (Integer.class, Boolean.class, etc).
    088     *
    089     * @param cls the target class of the converted value
    090     * @param value the value to convert
    091     * @param convHandler the conversion handler object
    092     * @return the converted value
    093     * @throws ConversionException if the value is not compatible with the requested type
    094     */
    095    public static Object to(final Class<?> cls, final Object value, final DefaultConversionHandler convHandler) throws ConversionException {
    096        if (cls.isInstance(value)) {
    097            return value; // no conversion needed
    098        }
    099
    100        if (String.class.equals(cls)) {
    101            return String.valueOf(value);
    102        }
    103        if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls)) {
    104            return toBoolean(value);
    105        }
    106        if (Character.class.equals(cls) || Character.TYPE.equals(cls)) {
    107            return toCharacter(value);
    108        }
    109        if (Number.class.isAssignableFrom(cls) || cls.isPrimitive()) {
    110            if (Integer.class.equals(cls) || Integer.TYPE.equals(cls)) {
    111                return toInteger(value);
    112            }
    113            if (Long.class.equals(cls) || Long.TYPE.equals(cls)) {
    114                return toLong(value);
    115            }
    116            if (Byte.class.equals(cls) || Byte.TYPE.equals(cls)) {
    117                return toByte(value);
    118            }
    119            if (Short.class.equals(cls) || Short.TYPE.equals(cls)) {
    120                return toShort(value);
    121            }
    122            if (Float.class.equals(cls) || Float.TYPE.equals(cls)) {
    123                return toFloat(value);
    124            }
    125            if (Double.class.equals(cls) || Double.TYPE.equals(cls)) {
    126                return toDouble(value);
    127            }
    128            if (BigInteger.class.equals(cls)) {
    129                return toBigInteger(value);
    130            }
    131            if (BigDecimal.class.equals(cls)) {
    132                return toBigDecimal(value);
    133            }
    134        } else if (Date.class.equals(cls)) {
    135            return toDate(value, convHandler.getDateFormat());
    136        } else if (Calendar.class.equals(cls)) {
    137            return toCalendar(value, convHandler.getDateFormat());
    138        } else if (File.class.equals(cls)) {
    139            return toFile(value);
    140        } else if (Path.class.equals(cls)) {
    141            return toPath(value);
    142        } else if (URI.class.equals(cls)) {
    143            return toURI(value);
    144        } else if (URL.class.equals(cls)) {
    145            return toURL(value);
    146        } else if (Pattern.class.equals(cls)) {
    147            return toPattern(value);
    148        } else if (Locale.class.equals(cls)) {
    149            return toLocale(value);
    150        } else if (cls.isEnum()) {
    151            return convertToEnum(cls, value);
    152        } else if (Color.class.equals(cls)) {
    153            return toColor(value);
    154        } else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME_JAVAX)) {
    155            // javamail-1.* With javax.mail.* namespace.
    156            return toInternetAddress(value, INTERNET_ADDRESS_CLASSNAME_JAVAX);
    157        } else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME_JAKARTA)) {
    158            // javamail-2.0+, with jakarta.mail.* namespace.
    159            return toInternetAddress(value, INTERNET_ADDRESS_CLASSNAME_JAKARTA);
    160        } else if (InetAddress.class.isAssignableFrom(cls)) {
    161            return toInetAddress(value);
    162        } else if (Duration.class.equals(cls)) {
    163            return toDuration(value);
    164        }
    165
    166        throw new ConversionException("The value '" + value + "' (" + value.getClass() + ")" + " can't be converted to a " + cls.getName() + " object");
    167    }
    168
    169    /**
    170     * Converts the specified object into a Boolean. Internally the {@code org.apache.commons.lang.BooleanUtils} class from
    171     * the <a href="https://commons.apache.org/lang/">Commons Lang</a> project is used to perform this conversion. This
    172     * class accepts some more tokens for the boolean value of <b>true</b>, e.g. {@code yes} and {@code on}. Please refer to
    173     * the documentation of this class for more details.
    174     *
    175     * @param value the value to convert
    176     * @return the converted value
    177     * @throws ConversionException thrown if the value cannot be converted to a boolean
    178     */
    179    public static Boolean toBoolean(final Object value) throws ConversionException {
    180        if (value instanceof Boolean) {
    181            return (Boolean) value;
    182        }
    183        if (!(value instanceof String)) {
    184            throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
    185        }
    186        final Boolean b = BooleanUtils.toBooleanObject((String) value);
    187        if (b == null) {
    188            throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
    189        }
    190        return b;
    191    }
    192
    193    /**
    194     * Converts the specified value object to a {@code Character}. This method converts the passed in object to a string. If
    195     * the string has exactly one character, this character is returned as result. Otherwise, conversion fails.
    196     *
    197     * @param value the value to be converted
    198     * @return the resulting {@code Character} object
    199     * @throws ConversionException if the conversion is not possible
    200     */
    201    public static Character toCharacter(final Object value) throws ConversionException {
    202        final String strValue = String.valueOf(value);
    203        if (strValue.length() == 1) {
    204            return Character.valueOf(strValue.charAt(0));
    205        }
    206        throw new ConversionException(String.format("The value '%s' cannot be converted to a Character object!", strValue));
    207    }
    208
    209    /**
    210     * Converts the specified object into a Byte.
    211     *
    212     * @param value the value to convert
    213     * @return the converted value
    214     * @throws ConversionException thrown if the value cannot be converted to a byte
    215     */
    216    public static Byte toByte(final Object value) throws ConversionException {
    217        final Number n = toNumber(value, Byte.class);
    218        if (n instanceof Byte) {
    219            return (Byte) n;
    220        }
    221        return n.byteValue();
    222    }
    223
    224    /**
    225     * Converts the specified object into a Short.
    226     *
    227     * @param value the value to convert
    228     * @return the converted value
    229     * @throws ConversionException thrown if the value cannot be converted to a short
    230     */
    231    public static Short toShort(final Object value) throws ConversionException {
    232        final Number n = toNumber(value, Short.class);
    233        if (n instanceof Short) {
    234            return (Short) n;
    235        }
    236        return n.shortValue();
    237    }
    238
    239    /**
    240     * Converts the specified object into an Integer.
    241     *
    242     * @param value the value to convert
    243     * @return the converted value
    244     * @throws ConversionException thrown if the value cannot be converted to an integer
    245     */
    246    public static Integer toInteger(final Object value) throws ConversionException {
    247        final Number n = toNumber(value, Integer.class);
    248        if (n instanceof Integer) {
    249            return (Integer) n;
    250        }
    251        return n.intValue();
    252    }
    253
    254    /**
    255     * Converts the specified object into a Long.
    256     *
    257     * @param value the value to convert
    258     * @return the converted value
    259     * @throws ConversionException thrown if the value cannot be converted to a Long
    260     */
    261    public static Long toLong(final Object value) throws ConversionException {
    262        final Number n = toNumber(value, Long.class);
    263        if (n instanceof Long) {
    264            return (Long) n;
    265        }
    266        return n.longValue();
    267    }
    268
    269    /**
    270     * Converts the specified object into a Float.
    271     *
    272     * @param value the value to convert
    273     * @return the converted value
    274     * @throws ConversionException thrown if the value cannot be converted to a Float
    275     */
    276    public static Float toFloat(final Object value) throws ConversionException {
    277        final Number n = toNumber(value, Float.class);
    278        if (n instanceof Float) {
    279            return (Float) n;
    280        }
    281        return Float.valueOf(n.floatValue());
    282    }
    283
    284    /**
    285     * Converts the specified object into a Double.
    286     *
    287     * @param value the value to convert
    288     * @return the converted value
    289     * @throws ConversionException thrown if the value cannot be converted to a Double
    290     */
    291    public static Double toDouble(final Object value) throws ConversionException {
    292        final Number n = toNumber(value, Double.class);
    293        if (n instanceof Double) {
    294            return (Double) n;
    295        }
    296        return Double.valueOf(n.doubleValue());
    297    }
    298
    299    /**
    300     * Converts the specified object into a Duration.
    301     *
    302     * @param value the value to convert
    303     * @return the converted value
    304     * @throws ConversionException thrown if the value cannot be converted to a Duration
    305     * @since 2.8.0
    306     */
    307    public static Duration toDuration(final Object value) throws ConversionException {
    308        if (value instanceof Duration) {
    309            return (Duration) value;
    310        }
    311        if (value instanceof CharSequence) {
    312            try {
    313                return Duration.parse((CharSequence) value);
    314            } catch (final DateTimeParseException e) {
    315                throw new ConversionException("Could not convert " + value + " to Duration", e);
    316            }
    317        }
    318        throw new ConversionException("The value " + value + " can't be converted to a Duration");
    319    }
    320
    321    /**
    322     * Converts the specified object into a BigInteger.
    323     *
    324     * @param value the value to convert
    325     * @return the converted value
    326     * @throws ConversionException thrown if the value cannot be converted to a BigInteger
    327     */
    328    public static BigInteger toBigInteger(final Object value) throws ConversionException {
    329        final Number n = toNumber(value, BigInteger.class);
    330        if (n instanceof BigInteger) {
    331            return (BigInteger) n;
    332        }
    333        return BigInteger.valueOf(n.longValue());
    334    }
    335
    336    /**
    337     * Converts the specified object into a BigDecimal.
    338     *
    339     * @param value the value to convert
    340     * @return the converted value
    341     * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
    342     */
    343    public static BigDecimal toBigDecimal(final Object value) throws ConversionException {
    344        final Number n = toNumber(value, BigDecimal.class);
    345        if (n instanceof BigDecimal) {
    346            return (BigDecimal) n;
    347        }
    348        return BigDecimal.valueOf(n.doubleValue());
    349    }
    350
    351    /**
    352     * Tries to convert the specified object into a number object. This method is used by the conversion methods for number
    353     * types. Note that the return value is not in always of the specified target class, but only if a new object has to be
    354     * created.
    355     *
    356     * @param value the value to be converted (must not be <b>null</b>)
    357     * @param targetClass the target class of the conversion (must be derived from {@link Number})
    358     * @return the converted number
    359     * @throws ConversionException if the object cannot be converted
    360     */
    361    static Number toNumber(final Object value, final Class<?> targetClass) throws ConversionException {
    362        if (value instanceof Number) {
    363            return (Number) value;
    364        }
    365        final String str = value.toString();
    366        if (str.startsWith(HEX_PREFIX)) {
    367            try {
    368                return new BigInteger(str.substring(HEX_PREFIX.length()), HEX_RADIX);
    369            } catch (final NumberFormatException nex) {
    370                throw new ConversionException("Could not convert " + str + " to " + targetClass.getName() + "! Invalid hex number.", nex);
    371            }
    372        }
    373
    374        if (str.startsWith(BIN_PREFIX)) {
    375            try {
    376                return new BigInteger(str.substring(BIN_PREFIX.length()), BIN_RADIX);
    377            } catch (final NumberFormatException nex) {
    378                throw new ConversionException("Could not convert " + str + " to " + targetClass.getName() + "! Invalid binary number.", nex);
    379            }
    380        }
    381
    382        try {
    383            final Constructor<?> constr = targetClass.getConstructor(CONSTR_ARGS);
    384            return (Number) constr.newInstance(str);
    385        } catch (final InvocationTargetException itex) {
    386            throw new ConversionException("Could not convert " + str + " to " + targetClass.getName(), itex.getTargetException());
    387        } catch (final Exception ex) {
    388            // Treat all possible exceptions the same way
    389            throw new ConversionException("Conversion error when trying to convert " + str + " to " + targetClass.getName(), ex);
    390        }
    391    }
    392
    393    /**
    394     * Converts the specified object into a File.
    395     *
    396     * @param value the value to convert
    397     * @return the converted value
    398     * @throws ConversionException thrown if the value cannot be converted to a File
    399     * @since 2.3
    400     */
    401    public static File toFile(final Object value) throws ConversionException {
    402        if (value instanceof File) {
    403            return (File) value;
    404        }
    405        if (value instanceof Path) {
    406            return ((Path) value).toFile();
    407        }
    408        if (value instanceof String) {
    409            return new File((String) value);
    410        }
    411        throw new ConversionException("The value " + value + " can't be converted to a File");
    412    }
    413
    414    /**
    415     * Converts the specified object into a Path.
    416     *
    417     * @param value the value to convert
    418     * @return the converted value
    419     * @throws ConversionException thrown if the value cannot be converted to a Path
    420     * @since 2.3
    421     */
    422    public static Path toPath(final Object value) throws ConversionException {
    423        if (value instanceof File) {
    424            return ((File) value).toPath();
    425        }
    426        if (value instanceof Path) {
    427            return (Path) value;
    428        }
    429        if (value instanceof String) {
    430            return Paths.get((String) value);
    431        }
    432        throw new ConversionException("The value " + value + " can't be converted to a Path");
    433    }
    434
    435    /**
    436     * Converts the specified object into an URI.
    437     *
    438     * @param value the value to convert
    439     * @return the converted value
    440     * @throws ConversionException thrown if the value cannot be converted to an URI
    441     */
    442    public static URI toURI(final Object value) throws ConversionException {
    443        if (value instanceof URI) {
    444            return (URI) value;
    445        }
    446        if (!(value instanceof String)) {
    447            throw new ConversionException("The value " + value + " can't be converted to an URI");
    448        }
    449        try {
    450            return new URI((String) value);
    451        } catch (final URISyntaxException e) {
    452            throw new ConversionException("The value " + value + " can't be converted to an URI", e);
    453        }
    454    }
    455
    456    /**
    457     * Converts the specified object into an URL.
    458     *
    459     * @param value the value to convert
    460     * @return the converted value
    461     * @throws ConversionException thrown if the value cannot be converted to an URL
    462     */
    463    public static URL toURL(final Object value) throws ConversionException {
    464        if (value instanceof URL) {
    465            return (URL) value;
    466        }
    467        if (!(value instanceof String)) {
    468            throw new ConversionException("The value " + value + " can't be converted to an URL");
    469        }
    470        try {
    471            return new URL((String) value);
    472        } catch (final MalformedURLException e) {
    473            throw new ConversionException("The value " + value + " can't be converted to an URL", e);
    474        }
    475    }
    476
    477    /**
    478     * Converts the specified object into a Pattern.
    479     *
    480     * @param value the value to convert
    481     * @return the converted value
    482     * @throws ConversionException thrown if the value cannot be converted to a Pattern
    483     */
    484    public static Pattern toPattern(final Object value) throws ConversionException {
    485        if (value instanceof Pattern) {
    486            return (Pattern) value;
    487        }
    488        if (!(value instanceof String)) {
    489            throw new ConversionException("The value " + value + " can't be converted to a Pattern");
    490        }
    491        try {
    492            return Pattern.compile((String) value);
    493        } catch (final PatternSyntaxException e) {
    494            throw new ConversionException("The value " + value + " can't be converted to a Pattern", e);
    495        }
    496    }
    497
    498    /**
    499     * Converts the specified object into a Locale.
    500     *
    501     * @param value the value to convert
    502     * @return the converted value
    503     * @throws ConversionException thrown if the value cannot be converted to a Locale
    504     */
    505    public static Locale toLocale(final Object value) throws ConversionException {
    506        if (value instanceof Locale) {
    507            return (Locale) value;
    508        }
    509        if (!(value instanceof String)) {
    510            throw new ConversionException("The value " + value + " can't be converted to a Locale");
    511        }
    512        final String[] elements = ((String) value).split("_");
    513        final int size = elements.length;
    514
    515        if (size >= 1 && (elements[0].length() == 2 || elements[0].isEmpty())) {
    516            final String language = elements[0];
    517            final String country = size >= 2 ? elements[1] : "";
    518            final String variant = size >= 3 ? elements[2] : "";
    519
    520            return new Locale(language, country, variant);
    521        }
    522        throw new ConversionException("The value " + value + " can't be converted to a Locale");
    523    }
    524
    525    /**
    526     * Converts the specified object into a Color. If the value is a String, the format allowed is
    527     * (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
    528     * <ul>
    529     * <li>FF0000 (red)</li>
    530     * <li>0000FFA0 (semi transparent blue)</li>
    531     * <li>#CCCCCC (gray)</li>
    532     * <li>#00FF00A0 (semi transparent green)</li>
    533     * </ul>
    534     *
    535     * @param value the value to convert
    536     * @return the converted value
    537     * @throws ConversionException thrown if the value cannot be converted to a Color
    538     */
    539    public static Color toColor(final Object value) throws ConversionException {
    540        if (value instanceof Color) {
    541            return (Color) value;
    542        }
    543        if (!(value instanceof String) || StringUtils.isBlank((String) value)) {
    544            throw new ConversionException("The value " + value + " can't be converted to a Color");
    545        }
    546        String color = ((String) value).trim();
    547
    548        final int[] components = new int[3];
    549
    550        // check the size of the string
    551        final int minlength = components.length * 2;
    552        if (color.length() < minlength) {
    553            throw new ConversionException("The value " + value + " can't be converted to a Color");
    554        }
    555
    556        // remove the leading #
    557        if (color.startsWith("#")) {
    558            color = color.substring(1);
    559        }
    560
    561        try {
    562            // parse the components
    563            for (int i = 0; i < components.length; i++) {
    564                components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
    565            }
    566
    567            // parse the transparency
    568            final int alpha;
    569            if (color.length() >= minlength + 2) {
    570                alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
    571            } else {
    572                alpha = Color.black.getAlpha();
    573            }
    574
    575            return new Color(components[0], components[1], components[2], alpha);
    576        } catch (final Exception e) {
    577            throw new ConversionException("The value " + value + " can't be converted to a Color", e);
    578        }
    579    }
    580
    581    /**
    582     * Converts the specified value into an internet address.
    583     *
    584     * @param value the value to convert
    585     * @return the converted value
    586     * @throws ConversionException thrown if the value cannot be converted to a InetAddress
    587     *
    588     * @since 1.5
    589     */
    590    static InetAddress toInetAddress(final Object value) throws ConversionException {
    591        if (value instanceof InetAddress) {
    592            return (InetAddress) value;
    593        }
    594        if (!(value instanceof String)) {
    595            throw new ConversionException("The value " + value + " can't be converted to a InetAddress");
    596        }
    597        try {
    598            return InetAddress.getByName((String) value);
    599        } catch (final UnknownHostException e) {
    600            throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e);
    601        }
    602    }
    603
    604    /**
    605     * Converts the specified value into an email address with the given class name.
    606     *
    607     * @param value the value to convert
    608     * @param targetClassName the fully qualified name of the {@code InternetAddress} class to convert to, e.g.,
    609     *      {@value #INTERNET_ADDRESS_CLASSNAME_JAVAX} or {@value #INTERNET_ADDRESS_CLASSNAME_JAKARTA}
    610     * @return the converted value
    611     * @throws ConversionException thrown if the value cannot be converted to an email address
    612     *
    613     * @since 1.5
    614     */
    615    static Object toInternetAddress(final Object value, final String targetClassName) throws ConversionException {
    616        if (value.getClass().getName().equals(targetClassName)) {
    617            return value;
    618        }
    619        if (!(value instanceof String)) {
    620            throw new ConversionException("The value " + value + " can't be converted to an InternetAddress");
    621        }
    622        try {
    623            final Constructor<?> ctor = Class.forName(targetClassName).getConstructor(String.class);
    624            return ctor.newInstance(value);
    625        } catch (final Exception e) {
    626            throw new ConversionException("The value " + value + " can't be converted to an InternetAddress", e);
    627        }
    628    }
    629
    630    /**
    631     * Converts the specified value into an {@link Enum}.
    632     *
    633     * @param value the value to convert
    634     * @param cls the type of the enumeration
    635     * @return the converted value
    636     * @throws ConversionException thrown if the value cannot be converted to an enumeration
    637     *
    638     * @since 1.5
    639     */
    640    static <E extends Enum<E>> E toEnum(final Object value, final Class<E> cls) throws ConversionException {
    641        if (value.getClass().equals(cls)) {
    642            return cls.cast(value);
    643        }
    644        if (value instanceof String) {
    645            try {
    646                return Enum.valueOf(cls, (String) value);
    647            } catch (final Exception e) {
    648                throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
    649            }
    650        }
    651        if (!(value instanceof Number)) {
    652            throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
    653        }
    654        try {
    655            final E[] enumConstants = cls.getEnumConstants();
    656            return enumConstants[((Number) value).intValue()];
    657        } catch (final Exception e) {
    658            throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
    659        }
    660    }
    661
    662    /**
    663     * Converts the specified object into a Date.
    664     *
    665     * @param value the value to convert
    666     * @param format the DateFormat pattern to parse String values
    667     * @return the converted value
    668     * @throws ConversionException thrown if the value cannot be converted to a Calendar
    669     */
    670    public static Date toDate(final Object value, final String format) throws ConversionException {
    671        if (value instanceof Date) {
    672            return (Date) value;
    673        }
    674        if (value instanceof Calendar) {
    675            return ((Calendar) value).getTime();
    676        }
    677        if (!(value instanceof String)) {
    678            throw new ConversionException("The value " + value + " can't be converted to a Date");
    679        }
    680        try {
    681            return new SimpleDateFormat(format).parse((String) value);
    682        } catch (final ParseException e) {
    683            throw new ConversionException("The value " + value + " can't be converted to a Date", e);
    684        }
    685    }
    686
    687    /**
    688     * Converts the specified object into a Calendar.
    689     *
    690     * @param value the value to convert
    691     * @param format the DateFormat pattern to parse String values
    692     * @return the converted value
    693     * @throws ConversionException thrown if the value cannot be converted to a Calendar
    694     */
    695    public static Calendar toCalendar(final Object value, final String format) throws ConversionException {
    696        if (value instanceof Calendar) {
    697            return (Calendar) value;
    698        }
    699        if (value instanceof Date) {
    700            final Calendar calendar = Calendar.getInstance();
    701            calendar.setTime((Date) value);
    702            return calendar;
    703        }
    704        if (!(value instanceof String)) {
    705            throw new ConversionException("The value " + value + " can't be converted to a Calendar");
    706        }
    707        try {
    708            final Calendar calendar = Calendar.getInstance();
    709            calendar.setTime(new SimpleDateFormat(format).parse((String) value));
    710            return calendar;
    711        } catch (final ParseException e) {
    712            throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
    713        }
    714    }
    715
    716    /**
    717     * Converts a value to a constant of an enumeration class.
    718     *
    719     * @param enumClass the enumeration class
    720     * @param value the value to be converted
    721     * @return the converted value
    722     */
    723    @SuppressWarnings("unchecked")
    724    // conversion is safe because we know that the class is an Enum class
    725    private static Object convertToEnum(final Class<?> enumClass, final Object value) {
    726        return toEnum(value, enumClass.asSubclass(Enum.class));
    727    }
    728}