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