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