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    *     http://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.configuration;
19  
20  import java.awt.Color;
21  import java.lang.reflect.Array;
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.URL;
29  import java.net.UnknownHostException;
30  import java.text.ParseException;
31  import java.text.SimpleDateFormat;
32  import java.util.ArrayList;
33  import java.util.Calendar;
34  import java.util.Collection;
35  import java.util.Date;
36  import java.util.Iterator;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Locale;
40  
41  import org.apache.commons.lang.BooleanUtils;
42  import org.apache.commons.lang.StringUtils;
43  
44  /**
45   * A utility class to convert the configuration properties into any type.
46   *
47   * @author Emmanuel Bourg
48   * @version $Id: PropertyConverter.java 1301990 2012-03-17 20:10:46Z oheger $
49   * @since 1.1
50   */
51  public final class PropertyConverter
52  {
53      /** Constant for the list delimiter as char.*/
54      static final char LIST_ESC_CHAR = '\\';
55  
56      /** Constant for the list delimiter escaping character as string.*/
57      static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR);
58  
59      /** Constant for the prefix of hex numbers.*/
60      private static final String HEX_PREFIX = "0x";
61  
62      /** Constant for the radix of hex numbers.*/
63      private static final int HEX_RADIX = 16;
64  
65      /** Constant for the prefix of binary numbers.*/
66      private static final String BIN_PREFIX = "0b";
67  
68      /** Constant for the radix of binary numbers.*/
69      private static final int BIN_RADIX = 2;
70  
71      /** Constant for the argument classes of the Number constructor that takes a String. */
72      private static final Class<?>[] CONSTR_ARGS = {String.class};
73  
74      /** The fully qualified name of {@link javax.mail.internet.InternetAddress} */
75      private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress";
76  
77      /**
78       * Private constructor prevents instances from being created.
79       */
80      private PropertyConverter()
81      {
82          // to prevent instantiation...
83      }
84  
85      /**
86       * Converts the specified value to the target class. If the class is a
87       * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned
88       * will use the wrapper type (Integer.class, Boolean.class, etc).
89       *
90       * @param cls   the target class of the converted value
91       * @param value the value to convert
92       * @param params optional parameters used for the conversion
93       * @return the converted value
94       * @throws ConversionException if the value is not compatible with the requested type
95       *
96       * @since 1.5
97       */
98      static Object to(Class<?> cls, Object value, Object[] params) throws ConversionException
99      {
100         if (cls.isInstance(value))
101         {
102             return value; // no conversion needed
103         }
104 
105         if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls))
106         {
107             return toBoolean(value);
108         }
109         else if (Number.class.isAssignableFrom(cls) || cls.isPrimitive())
110         {
111             if (Integer.class.equals(cls) || Integer.TYPE.equals(cls))
112             {
113                 return toInteger(value);
114             }
115             else if (Long.class.equals(cls) || Long.TYPE.equals(cls))
116             {
117                 return toLong(value);
118             }
119             else if (Byte.class.equals(cls) || Byte.TYPE.equals(cls))
120             {
121                 return toByte(value);
122             }
123             else if (Short.class.equals(cls) || Short.TYPE.equals(cls))
124             {
125                 return toShort(value);
126             }
127             else if (Float.class.equals(cls) || Float.TYPE.equals(cls))
128             {
129                 return toFloat(value);
130             }
131             else if (Double.class.equals(cls) || Double.TYPE.equals(cls))
132             {
133                 return toDouble(value);
134             }
135             else if (BigInteger.class.equals(cls))
136             {
137                 return toBigInteger(value);
138             }
139             else if (BigDecimal.class.equals(cls))
140             {
141                 return toBigDecimal(value);
142             }
143         }
144         else if (Date.class.equals(cls))
145         {
146             return toDate(value, (String) params[0]);
147         }
148         else if (Calendar.class.equals(cls))
149         {
150             return toCalendar(value, (String) params[0]);
151         }
152         else if (URL.class.equals(cls))
153         {
154             return toURL(value);
155         }
156         else if (Locale.class.equals(cls))
157         {
158             return toLocale(value);
159         }
160         else if (isEnum(cls))
161         {
162             return convertToEnum(cls, value);
163         }
164         else if (Color.class.equals(cls))
165         {
166             return toColor(value);
167         }
168         else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME))
169         {
170             return toInternetAddress(value);
171         }
172         else if (InetAddress.class.isAssignableFrom(cls))
173         {
174             return toInetAddress(value);
175         }
176 
177         throw new ConversionException("The value '" + value + "' (" + value.getClass() + ")"
178                 + " can't be converted to a " + cls.getName() + " object");
179     }
180 
181     /**
182      * Convert the specified object into a Boolean. Internally the
183      * {@code org.apache.commons.lang.BooleanUtils} class from the
184      * <a href="http://commons.apache.org/lang/">Commons Lang</a>
185      * project is used to perform this conversion. This class accepts some more
186      * tokens for the boolean value of <b>true</b>, e.g. {@code yes} and
187      * {@code on}. Please refer to the documentation of this class for more
188      * details.
189      *
190      * @param value the value to convert
191      * @return the converted value
192      * @throws ConversionException thrown if the value cannot be converted to a boolean
193      */
194     public static Boolean toBoolean(Object value) throws ConversionException
195     {
196         if (value instanceof Boolean)
197         {
198             return (Boolean) value;
199         }
200         else if (value instanceof String)
201         {
202             Boolean b = BooleanUtils.toBooleanObject((String) value);
203             if (b == null)
204             {
205                 throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
206             }
207             return b;
208         }
209         else
210         {
211             throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
212         }
213     }
214 
215     /**
216      * Convert the specified object into a Byte.
217      *
218      * @param value the value to convert
219      * @return the converted value
220      * @throws ConversionException thrown if the value cannot be converted to a byte
221      */
222     public static Byte toByte(Object value) throws ConversionException
223     {
224         Number n = toNumber(value, Byte.class);
225         if (n instanceof Byte)
226         {
227             return (Byte) n;
228         }
229         else
230         {
231             return new Byte(n.byteValue());
232         }
233     }
234 
235     /**
236      * Convert the specified object into a Short.
237      *
238      * @param value the value to convert
239      * @return the converted value
240      * @throws ConversionException thrown if the value cannot be converted to a short
241      */
242     public static Short toShort(Object value) throws ConversionException
243     {
244         Number n = toNumber(value, Short.class);
245         if (n instanceof Short)
246         {
247             return (Short) n;
248         }
249         else
250         {
251             return new Short(n.shortValue());
252         }
253     }
254 
255     /**
256      * Convert the specified object into an Integer.
257      *
258      * @param value the value to convert
259      * @return the converted value
260      * @throws ConversionException thrown if the value cannot be converted to an integer
261      */
262     public static Integer toInteger(Object value) throws ConversionException
263     {
264         Number n = toNumber(value, Integer.class);
265         if (n instanceof Integer)
266         {
267             return (Integer) n;
268         }
269         else
270         {
271             return new Integer(n.intValue());
272         }
273     }
274 
275     /**
276      * Convert the specified object into a Long.
277      *
278      * @param value the value to convert
279      * @return the converted value
280      * @throws ConversionException thrown if the value cannot be converted to a Long
281      */
282     public static Long toLong(Object value) throws ConversionException
283     {
284         Number n = toNumber(value, Long.class);
285         if (n instanceof Long)
286         {
287             return (Long) n;
288         }
289         else
290         {
291             return new Long(n.longValue());
292         }
293     }
294 
295     /**
296      * Convert the specified object into a Float.
297      *
298      * @param value the value to convert
299      * @return the converted value
300      * @throws ConversionException thrown if the value cannot be converted to a Float
301      */
302     public static Float toFloat(Object value) throws ConversionException
303     {
304         Number n = toNumber(value, Float.class);
305         if (n instanceof Float)
306         {
307             return (Float) n;
308         }
309         else
310         {
311             return new Float(n.floatValue());
312         }
313     }
314 
315     /**
316      * Convert the specified object into a Double.
317      *
318      * @param value the value to convert
319      * @return the converted value
320      * @throws ConversionException thrown if the value cannot be converted to a Double
321      */
322     public static Double toDouble(Object value) throws ConversionException
323     {
324         Number n = toNumber(value, Double.class);
325         if (n instanceof Double)
326         {
327             return (Double) n;
328         }
329         else
330         {
331             return new Double(n.doubleValue());
332         }
333     }
334 
335     /**
336      * Convert the specified object into a BigInteger.
337      *
338      * @param value the value to convert
339      * @return the converted value
340      * @throws ConversionException thrown if the value cannot be converted to a BigInteger
341      */
342     public static BigInteger toBigInteger(Object value) throws ConversionException
343     {
344         Number n = toNumber(value, BigInteger.class);
345         if (n instanceof BigInteger)
346         {
347             return (BigInteger) n;
348         }
349         else
350         {
351             return BigInteger.valueOf(n.longValue());
352         }
353     }
354 
355     /**
356      * Convert the specified object into a BigDecimal.
357      *
358      * @param value the value to convert
359      * @return the converted value
360      * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
361      */
362     public static BigDecimal toBigDecimal(Object value) throws ConversionException
363     {
364         Number n = toNumber(value, BigDecimal.class);
365         if (n instanceof BigDecimal)
366         {
367             return (BigDecimal) n;
368         }
369         else
370         {
371             return new BigDecimal(n.doubleValue());
372         }
373     }
374 
375     /**
376      * Tries to convert the specified object into a number object. This method
377      * is used by the conversion methods for number types. Note that the return
378      * value is not in always of the specified target class, but only if a new
379      * object has to be created.
380      *
381      * @param value the value to be converted (must not be <b>null</b>)
382      * @param targetClass the target class of the conversion (must be derived
383      * from {@code java.lang.Number})
384      * @return the converted number
385      * @throws ConversionException if the object cannot be converted
386      */
387     static Number toNumber(Object value, Class<?> targetClass) throws ConversionException
388     {
389         if (value instanceof Number)
390         {
391             return (Number) value;
392         }
393         else
394         {
395             String str = value.toString();
396             if (str.startsWith(HEX_PREFIX))
397             {
398                 try
399                 {
400                     return new BigInteger(str.substring(HEX_PREFIX.length()), HEX_RADIX);
401                 }
402                 catch (NumberFormatException nex)
403                 {
404                     throw new ConversionException("Could not convert " + str
405                             + " to " + targetClass.getName()
406                             + "! Invalid hex number.", nex);
407                 }
408             }
409 
410             if (str.startsWith(BIN_PREFIX))
411             {
412                 try
413                 {
414                     return new BigInteger(str.substring(BIN_PREFIX.length()), BIN_RADIX);
415                 }
416                 catch (NumberFormatException nex)
417                 {
418                     throw new ConversionException("Could not convert " + str
419                             + " to " + targetClass.getName()
420                             + "! Invalid binary number.", nex);
421                 }
422             }
423 
424             try
425             {
426                 Constructor<?> constr = targetClass.getConstructor(CONSTR_ARGS);
427                 return (Number) constr.newInstance(new Object[]{str});
428             }
429             catch (InvocationTargetException itex)
430             {
431                 throw new ConversionException("Could not convert " + str
432                         + " to " + targetClass.getName(), itex
433                         .getTargetException());
434             }
435             catch (Exception ex)
436             {
437                 // Treat all possible exceptions the same way
438                 throw new ConversionException(
439                         "Conversion error when trying to convert " + str
440                                 + " to " + targetClass.getName(), ex);
441             }
442         }
443     }
444 
445     /**
446      * Convert the specified object into an URL.
447      *
448      * @param value the value to convert
449      * @return the converted value
450      * @throws ConversionException thrown if the value cannot be converted to an URL
451      */
452     public static URL toURL(Object value) throws ConversionException
453     {
454         if (value instanceof URL)
455         {
456             return (URL) value;
457         }
458         else if (value instanceof String)
459         {
460             try
461             {
462                 return new URL((String) value);
463             }
464             catch (MalformedURLException e)
465             {
466                 throw new ConversionException("The value " + value + " can't be converted to an URL", e);
467             }
468         }
469         else
470         {
471             throw new ConversionException("The value " + value + " can't be converted to an URL");
472         }
473     }
474 
475     /**
476      * Convert the specified object into a Locale.
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 Locale
481      */
482     public static Locale toLocale(Object value) throws ConversionException
483     {
484         if (value instanceof Locale)
485         {
486             return (Locale) value;
487         }
488         else if (value instanceof String)
489         {
490             List<String> elements = split((String) value, '_');
491             int size = elements.size();
492 
493             if (size >= 1 && ((elements.get(0)).length() == 2 || (elements.get(0)).length() == 0))
494             {
495                 String language = elements.get(0);
496                 String country = (size >= 2) ? elements.get(1) : "";
497                 String variant = (size >= 3) ? elements.get(2) : "";
498 
499                 return new Locale(language, country, variant);
500             }
501             else
502             {
503                 throw new ConversionException("The value " + value + " can't be converted to a Locale");
504             }
505         }
506         else
507         {
508             throw new ConversionException("The value " + value + " can't be converted to a Locale");
509         }
510     }
511 
512     /**
513      * Split a string on the specified delimiter. To be removed when
514      * commons-lang has a better replacement available (Tokenizer?).
515      *
516      * todo: replace with a commons-lang equivalent
517      *
518      * @param s          the string to split
519      * @param delimiter  the delimiter
520      * @param trim       a flag whether the single elements should be trimmed
521      * @return a list with the single tokens
522      */
523     public static List<String> split(String s, char delimiter, boolean trim)
524     {
525         if (s == null)
526         {
527             return new ArrayList<String>();
528         }
529 
530         List<String> list = new ArrayList<String>();
531 
532         StringBuilder token = new StringBuilder();
533         int begin = 0;
534         boolean inEscape = false;
535 
536         while (begin < s.length())
537         {
538             char c = s.charAt(begin);
539             if (inEscape)
540             {
541                 // last character was the escape marker
542                 // can current character be escaped?
543                 if (c != delimiter && c != LIST_ESC_CHAR)
544                 {
545                     // no, also add escape character
546                     token.append(LIST_ESC_CHAR);
547                 }
548                 token.append(c);
549                 inEscape = false;
550             }
551 
552             else
553             {
554                 if (c == delimiter)
555                 {
556                     // found a list delimiter -> add token and resetDefaultFileSystem buffer
557                     String t = token.toString();
558                     if (trim)
559                     {
560                         t = t.trim();
561                     }
562                     list.add(t);
563                     token = new StringBuilder();
564                 }
565                 else if (c == LIST_ESC_CHAR)
566                 {
567                     // eventually escape next character
568                     inEscape = true;
569                 }
570                 else
571                 {
572                     token.append(c);
573                 }
574             }
575 
576             begin++;
577         }
578 
579         // Trailing delimiter?
580         if (inEscape)
581         {
582             token.append(LIST_ESC_CHAR);
583         }
584         // Add last token
585         String t = token.toString();
586         if (trim)
587         {
588             t = t.trim();
589         }
590         list.add(t);
591 
592         return list;
593     }
594 
595     /**
596      * Split a string on the specified delimiter always trimming the elements.
597      * This is a shortcut for {@code split(s, delimiter, true)}.
598      *
599      * @param s          the string to split
600      * @param delimiter  the delimiter
601      * @return a list with the single tokens
602      */
603     public static List<String> split(String s, char delimiter)
604     {
605         return split(s, delimiter, true);
606     }
607 
608     /**
609      * Escapes the delimiters that might be contained in the given string. This
610      * method works like {@link #escapeListDelimiter(String, char)}. In addition,
611      * a single backslash will also be escaped.
612      *
613      * @param s the string with the value
614      * @param delimiter the list delimiter to use
615      * @return the correctly escaped string
616      */
617     public static String escapeDelimiters(String s, char delimiter)
618     {
619         String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE);
620         return escapeListDelimiter(s1, delimiter);
621     }
622 
623     /**
624      * Escapes the list delimiter if it is contained in the given string. This
625      * method ensures that list delimiter characters that are part of a
626      * property's value are correctly escaped when a configuration is saved to a
627      * file. Otherwise when loaded again the property will be treated as a list
628      * property.
629      *
630      * @param s the string with the value
631      * @param delimiter the list delimiter to use
632      * @return the escaped string
633      * @since 1.7
634      */
635     public static String escapeListDelimiter(String s, char delimiter)
636     {
637         return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE
638                 + delimiter);
639     }
640 
641     /**
642      * Convert the specified object into a Color. If the value is a String,
643      * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
644      * <ul>
645      *   <li>FF0000 (red)</li>
646      *   <li>0000FFA0 (semi transparent blue)</li>
647      *   <li>#CCCCCC (gray)</li>
648      *   <li>#00FF00A0 (semi transparent green)</li>
649      * </ul>
650      *
651      * @param value the value to convert
652      * @return the converted value
653      * @throws ConversionException thrown if the value cannot be converted to a Color
654      */
655     public static Color toColor(Object value) throws ConversionException
656     {
657         if (value instanceof Color)
658         {
659             return (Color) value;
660         }
661         else if (value instanceof String && !StringUtils.isBlank((String) value))
662         {
663             String color = ((String) value).trim();
664 
665             int[] components = new int[3];
666 
667             // check the size of the string
668             int minlength = components.length * 2;
669             if (color.length() < minlength)
670             {
671                 throw new ConversionException("The value " + value + " can't be converted to a Color");
672             }
673 
674             // remove the leading #
675             if (color.startsWith("#"))
676             {
677                 color = color.substring(1);
678             }
679 
680             try
681             {
682                 // parse the components
683                 for (int i = 0; i < components.length; i++)
684                 {
685                     components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
686                 }
687 
688                 // parse the transparency
689                 int alpha;
690                 if (color.length() >= minlength + 2)
691                 {
692                     alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
693                 }
694                 else
695                 {
696                     alpha = Color.black.getAlpha();
697                 }
698 
699                 return new Color(components[0], components[1], components[2], alpha);
700             }
701             catch (Exception e)
702             {
703                 throw new ConversionException("The value " + value + " can't be converted to a Color", e);
704             }
705         }
706         else
707         {
708             throw new ConversionException("The value " + value + " can't be converted to a Color");
709         }
710     }
711 
712     /**
713      * Convert the specified value into an internet address.
714      *
715      * @param value the value to convert
716      * @return the converted value
717      * @throws ConversionException thrown if the value cannot be converted to a InetAddress
718      *
719      * @since 1.5
720      */
721     static InetAddress toInetAddress(Object value) throws ConversionException
722     {
723         if (value instanceof InetAddress)
724         {
725             return (InetAddress) value;
726         }
727         else if (value instanceof String)
728         {
729             try
730             {
731                 return InetAddress.getByName((String) value);
732             }
733             catch (UnknownHostException e)
734             {
735                 throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e);
736             }
737         }
738         else
739         {
740             throw new ConversionException("The value " + value + " can't be converted to a InetAddress");
741         }
742     }
743 
744     /**
745      * Convert the specified value into an email address.
746      *
747      * @param value the value to convert
748      * @return the converted value
749      * @throws ConversionException thrown if the value cannot be converted to an email address
750      *
751      * @since 1.5
752      */
753     static Object toInternetAddress(Object value) throws ConversionException
754     {
755         if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME))
756         {
757             return value;
758         }
759         else if (value instanceof String)
760         {
761             try
762             {
763                 Constructor<?> ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME)
764                         .getConstructor(new Class[] {String.class});
765                 return ctor.newInstance(new Object[] {value});
766             }
767             catch (Exception e)
768             {
769                 throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e);
770             }
771         }
772         else
773         {
774             throw new ConversionException("The value " + value + " can't be converted to a InternetAddress");
775         }
776     }
777 
778     /**
779      * Calls Class.isEnum() on Java 5, returns false on older JRE.
780      */
781     static boolean isEnum(Class<?> cls)
782     {
783         return cls.isEnum();
784     }
785 
786     /**
787      * Convert the specified value into a Java 5 enum.
788      *
789      * @param value the value to convert
790      * @param cls   the type of the enumeration
791      * @return the converted value
792      * @throws ConversionException thrown if the value cannot be converted to an enumeration
793      *
794      * @since 1.5
795      */
796     static <E extends Enum<E>> E toEnum(Object value, Class<E> cls) throws ConversionException
797     {
798         if (value.getClass().equals(cls))
799         {
800             return cls.cast(value);
801         }
802         else if (value instanceof String)
803         {
804             try
805             {
806                 return Enum.valueOf(cls, (String) value);
807             }
808             catch (Exception e)
809             {
810                 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
811             }
812         }
813         else if (value instanceof Number)
814         {
815             try
816             {
817                 E[] enumConstants = cls.getEnumConstants();
818                 return enumConstants[((Number) value).intValue()];
819             }
820             catch (Exception e)
821             {
822                 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
823             }
824         }
825         else
826         {
827             throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
828         }
829     }
830 
831     /**
832      * Convert the specified object into a Date.
833      *
834      * @param value  the value to convert
835      * @param format the DateFormat pattern to parse String values
836      * @return the converted value
837      * @throws ConversionException thrown if the value cannot be converted to a Calendar
838      */
839     public static Date toDate(Object value, String format) throws ConversionException
840     {
841         if (value instanceof Date)
842         {
843             return (Date) value;
844         }
845         else if (value instanceof Calendar)
846         {
847             return ((Calendar) value).getTime();
848         }
849         else if (value instanceof String)
850         {
851             try
852             {
853                 return new SimpleDateFormat(format).parse((String) value);
854             }
855             catch (ParseException e)
856             {
857                 throw new ConversionException("The value " + value + " can't be converted to a Date", e);
858             }
859         }
860         else
861         {
862             throw new ConversionException("The value " + value + " can't be converted to a Date");
863         }
864     }
865 
866     /**
867      * Convert the specified object into a Calendar.
868      *
869      * @param value  the value to convert
870      * @param format the DateFormat pattern to parse String values
871      * @return the converted value
872      * @throws ConversionException thrown if the value cannot be converted to a Calendar
873      */
874     public static Calendar toCalendar(Object value, String format) throws ConversionException
875     {
876         if (value instanceof Calendar)
877         {
878             return (Calendar) value;
879         }
880         else if (value instanceof Date)
881         {
882             Calendar calendar = Calendar.getInstance();
883             calendar.setTime((Date) value);
884             return calendar;
885         }
886         else if (value instanceof String)
887         {
888             try
889             {
890                 Calendar calendar = Calendar.getInstance();
891                 calendar.setTime(new SimpleDateFormat(format).parse((String) value));
892                 return calendar;
893             }
894             catch (ParseException e)
895             {
896                 throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
897             }
898         }
899         else
900         {
901             throw new ConversionException("The value " + value + " can't be converted to a Calendar");
902         }
903     }
904 
905     /**
906      * Returns an iterator over the simple values of a composite value. This
907      * implementation calls {@link #flatten(Object, char)} and
908      * returns an iterator over the returned collection.
909      *
910      * @param value the value to "split"
911      * @param delimiter the delimiter for String values
912      * @return an iterator for accessing the single values
913      */
914     public static Iterator<?> toIterator(Object value, char delimiter)
915     {
916         return flatten(value, delimiter).iterator();
917     }
918 
919     /**
920      * Returns a collection with all values contained in the specified object.
921      * This method is used for instance by the {@code addProperty()}
922      * implementation of the default configurations to gather all values of the
923      * property to add. Depending on the type of the passed in object the
924      * following things happen:
925      * <ul>
926      * <li>Strings are checked for delimiter characters and split if necessary.</li>
927      * <li>For objects implementing the {@code Iterable} interface, the
928      * corresponding {@code Iterator} is obtained, and contained elements
929      * are added to the resulting collection.</li>
930      * <li>Arrays are treated as {@code Iterable} objects.</li>
931      * <li>All other types are directly inserted.</li>
932      * <li>Recursive combinations are supported, e.g. a collection containing
933      * an array that contains strings: The resulting collection will only
934      * contain primitive objects (hence the name &quot;flatten&quot;).</li>
935      * </ul>
936      *
937      * @param value the value to be processed
938      * @param delimiter the delimiter for String values
939      * @return a &quot;flat&quot; collection containing all primitive values of
940      *         the passed in object
941      */
942     private static Collection<?> flatten(Object value, char delimiter)
943     {
944         if (value instanceof String)
945         {
946             String s = (String) value;
947             if (s.indexOf(delimiter) > 0)
948             {
949                 return split(s, delimiter);
950             }
951         }
952 
953         Collection<Object> result = new LinkedList<Object>();
954         if (value instanceof Iterable)
955         {
956             flattenIterator(result, ((Iterable<?>) value).iterator(), delimiter);
957         }
958         else if (value instanceof Iterator)
959         {
960             flattenIterator(result, (Iterator<?>) value, delimiter);
961         }
962         else if (value != null)
963         {
964             if (value.getClass().isArray())
965             {
966                 for (int len = Array.getLength(value), idx = 0; idx < len; idx++)
967                 {
968                     result.addAll(flatten(Array.get(value, idx), delimiter));
969                 }
970             }
971             else
972             {
973                 result.add(value);
974             }
975         }
976 
977         return result;
978     }
979 
980     /**
981      * Flattens the given iterator. For each element in the iteration
982      * {@code flatten()} will be called recursively.
983      *
984      * @param target the target collection
985      * @param it the iterator to process
986      * @param delimiter the delimiter for String values
987      */
988     private static void flattenIterator(Collection<Object> target, Iterator<?> it, char delimiter)
989     {
990         while (it.hasNext())
991         {
992             target.addAll(flatten(it.next(), delimiter));
993         }
994     }
995 
996     /**
997      * Performs interpolation of the specified value. This method checks if the
998      * given value contains variables of the form <code>${...}</code>. If
999      * this is the case, all occurrences will be substituted by their current
1000      * values.
1001      *
1002      * @param value the value to be interpolated
1003      * @param config the current configuration object
1004      * @return the interpolated value
1005      */
1006     public static Object interpolate(Object value, AbstractConfiguration config)
1007     {
1008         if (value instanceof String)
1009         {
1010             return config.getSubstitutor().replace((String) value);
1011         }
1012         else
1013         {
1014             return value;
1015         }
1016     }
1017 
1018     /**
1019      * Helper method for converting a value to a constant of an enumeration
1020      * class.
1021      *
1022      * @param enumClass the enumeration class
1023      * @param value the value to be converted
1024      * @return the converted value
1025      */
1026     @SuppressWarnings("unchecked")
1027     // conversion is safe because we know that the class is an Enum class
1028     private static Object convertToEnum(Class<?> enumClass, Object value)
1029     {
1030         return toEnum(value, enumClass.asSubclass(Enum.class));
1031     }
1032 }