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}