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