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