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: 589380 $, $Date: 2007-10-28 17:37:35 +0100 (So, 28 Okt 2007) $
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      * @return a list with the single tokens
503      */
504     public static List split(String s, char delimiter)
505     {
506         if (s == null)
507         {
508             return new ArrayList();
509         }
510 
511         List list = new ArrayList();
512 
513         StringBuffer token = new StringBuffer();
514         int begin = 0;
515         boolean inEscape = false;
516 
517         while (begin < s.length())
518         {
519             char c = s.charAt(begin);
520             if (inEscape)
521             {
522                 // last character was the escape marker
523                 // can current character be escaped?
524                 if (c != delimiter && c != LIST_ESC_CHAR)
525                 {
526                     // no, also add escape character
527                     token.append(LIST_ESC_CHAR);
528                 }
529                 token.append(c);
530                 inEscape = false;
531             }
532 
533             else
534             {
535                 if (c == delimiter)
536                 {
537                     // found a list delimiter -> add token and reset buffer
538                     list.add(token.toString().trim());
539                     token = new StringBuffer();
540                 }
541                 else if (c == LIST_ESC_CHAR)
542                 {
543                     // eventually escape next character
544                     inEscape = true;
545                 }
546                 else
547                 {
548                     token.append(c);
549                 }
550             }
551 
552             begin++;
553         }
554 
555         // Trailing delimiter?
556         if (inEscape)
557         {
558             token.append(LIST_ESC_CHAR);
559         }
560         // Add last token
561         list.add(token.toString().trim());
562 
563         return list;
564     }
565 
566     /***
567      * Escapes the delimiters that might be contained in the given string. This
568      * method ensures that list delimiter characters that are part of a
569      * property's value are correctly escaped when a configuration is saved to a
570      * file. Otherwise when loaded again the property will be treated as a list
571      * property. A single backslash will also be escaped.
572      *
573      * @param s the string with the value
574      * @param delimiter the list delimiter to use
575      * @return the correctly esaped string
576      */
577     public static String escapeDelimiters(String s, char delimiter)
578     {
579         String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE);
580         return StringUtils.replace(s1, String.valueOf(delimiter), LIST_ESCAPE + delimiter);
581     }
582 
583     /***
584      * Convert the specified object into a Color. If the value is a String,
585      * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
586      * <ul>
587      *   <li>FF0000 (red)</li>
588      *   <li>0000FFA0 (semi transparent blue)</li>
589      *   <li>#CCCCCC (gray)</li>
590      *   <li>#00FF00A0 (semi transparent green)</li>
591      * </ul>
592      *
593      * @param value the value to convert
594      * @return the converted value
595      * @throws ConversionException thrown if the value cannot be converted to a Color
596      */
597     public static Color toColor(Object value) throws ConversionException
598     {
599         if (value instanceof Color)
600         {
601             return (Color) value;
602         }
603         else if (value instanceof String && !StringUtils.isBlank((String) value))
604         {
605             String color = ((String) value).trim();
606 
607             int[] components = new int[3];
608 
609             // check the size of the string
610             int minlength = components.length * 2;
611             if (color.length() < minlength)
612             {
613                 throw new ConversionException("The value " + value + " can't be converted to a Color");
614             }
615 
616             // remove the leading #
617             if (color.startsWith("#"))
618             {
619                 color = color.substring(1);
620             }
621 
622             try
623             {
624                 // parse the components
625                 for (int i = 0; i < components.length; i++)
626                 {
627                     components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
628                 }
629 
630                 // parse the transparency
631                 int alpha;
632                 if (color.length() >= minlength + 2)
633                 {
634                     alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
635                 }
636                 else
637                 {
638                     alpha = Color.black.getAlpha();
639                 }
640 
641                 return new Color(components[0], components[1], components[2], alpha);
642             }
643             catch (Exception e)
644             {
645                 throw new ConversionException("The value " + value + " can't be converted to a Color", e);
646             }
647         }
648         else
649         {
650             throw new ConversionException("The value " + value + " can't be converted to a Color");
651         }
652     }
653 
654     /***
655      * Convert the specified value into an internet address.
656      *
657      * @param value the value to convert
658      * @return the converted value
659      * @throws ConversionException thrown if the value cannot be converted to a InetAddress
660      *
661      * @since 1.5
662      */
663     static InetAddress toInetAddress(Object value) throws ConversionException
664     {
665         if (value instanceof InetAddress)
666         {
667             return (InetAddress) value;
668         }
669         else if (value instanceof String)
670         {
671             try
672             {
673                 return InetAddress.getByName((String) value);
674             }
675             catch (UnknownHostException e)
676             {
677                 throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e);
678             }
679         }
680         else
681         {
682             throw new ConversionException("The value " + value + " can't be converted to a InetAddress");
683         }
684     }
685 
686     /***
687      * Convert the specified value into an email address.
688      *
689      * @param value the value to convert
690      * @return the converted value
691      * @throws ConversionException thrown if the value cannot be converted to an email address
692      *
693      * @since 1.5
694      */
695     static Object toInternetAddress(Object value) throws ConversionException
696     {
697         if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME))
698         {
699             return value;
700         }
701         else if (value instanceof String)
702         {
703             try
704             {
705                 Constructor ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME).getConstructor(new Class[] {String.class});
706                 return ctor.newInstance(new Object[] {value});
707             }
708             catch (Exception e)
709             {
710                 throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e);
711             }
712         }
713         else
714         {
715             throw new ConversionException("The value " + value + " can't be converted to a InternetAddress");
716         }
717     }
718 
719     /***
720      * Calls Class.isEnum() on Java 5, returns false on older JRE.
721      */
722     static boolean isEnum(Class cls)
723     {
724         if (!SystemUtils.isJavaVersionAtLeast(JAVA_VERSION_1_5))
725         {
726             return false;
727         }
728 
729         try
730         {
731             Method isEnumMethod = Class.class.getMethod("isEnum", new Class[] {});
732             return ((Boolean) isEnumMethod.invoke(cls, new Object[] {})).booleanValue();
733         }
734         catch (Exception e)
735         {
736             // impossible
737             throw new RuntimeException(e.getMessage());
738         }
739     }
740 
741     /***
742      * Convert the specified value into a Java 5 enum.
743      *
744      * @param value the value to convert
745      * @param cls   the type of the enumeration
746      * @return the converted value
747      * @throws ConversionException thrown if the value cannot be converted to an enumeration
748      *
749      * @since 1.5
750      */
751     static Object toEnum(Object value, Class cls) throws ConversionException
752     {
753         if (value.getClass().equals(cls))
754         {
755             return value;
756         }
757         else if (value instanceof String)
758         {
759             try
760             {
761                 Method valueOfMethod = cls.getMethod("valueOf", new Class[] {String.class});
762                 return valueOfMethod.invoke(null, new Object[] {value});
763             }
764             catch (Exception e)
765             {
766                 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
767             }
768         }
769         else if (value instanceof Number)
770         {
771             try
772             {
773                 Method valuesMethod = cls.getMethod("values", new Class[] {});
774                 Object valuesArray = valuesMethod.invoke(null, new Object[] {});
775 
776                 return Array.get(valuesArray, ((Number) value).intValue());
777             }
778             catch (Exception e)
779             {
780                 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
781             }
782         }
783         else
784         {
785             throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
786         }
787     }
788 
789     /***
790      * Convert the specified object into a Date.
791      *
792      * @param value  the value to convert
793      * @param format the DateFormat pattern to parse String values
794      * @return the converted value
795      * @throws ConversionException thrown if the value cannot be converted to a Calendar
796      */
797     public static Date toDate(Object value, String format) throws ConversionException
798     {
799         if (value instanceof Date)
800         {
801             return (Date) value;
802         }
803         else if (value instanceof Calendar)
804         {
805             return ((Calendar) value).getTime();
806         }
807         else if (value instanceof String)
808         {
809             try
810             {
811                 return new SimpleDateFormat(format).parse((String) value);
812             }
813             catch (ParseException e)
814             {
815                 throw new ConversionException("The value " + value + " can't be converted to a Date", e);
816             }
817         }
818         else
819         {
820             throw new ConversionException("The value " + value + " can't be converted to a Date");
821         }
822     }
823 
824     /***
825      * Convert the specified object into a Calendar.
826      *
827      * @param value  the value to convert
828      * @param format the DateFormat pattern to parse String values
829      * @return the converted value
830      * @throws ConversionException thrown if the value cannot be converted to a Calendar
831      */
832     public static Calendar toCalendar(Object value, String format) throws ConversionException
833     {
834         if (value instanceof Calendar)
835         {
836             return (Calendar) value;
837         }
838         else if (value instanceof Date)
839         {
840             Calendar calendar = Calendar.getInstance();
841             calendar.setTime((Date) value);
842             return calendar;
843         }
844         else if (value instanceof String)
845         {
846             try
847             {
848                 Calendar calendar = Calendar.getInstance();
849                 calendar.setTime(new SimpleDateFormat(format).parse((String) value));
850                 return calendar;
851             }
852             catch (ParseException e)
853             {
854                 throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
855             }
856         }
857         else
858         {
859             throw new ConversionException("The value " + value + " can't be converted to a Calendar");
860         }
861     }
862 
863     /***
864      * Return an iterator over the simple values of a composite value. The value
865      * specified is handled depending on its type:
866      * <ul>
867      *   <li>Strings are checked for delimiter characters and splitted if necessary.</li>
868      *   <li>For collections the single elements are checked.</li>
869      *   <li>Arrays are treated like collections.</li>
870      *   <li>All other types are directly inserted.</li>
871      *   <li>Recursive combinations are supported, e.g. a collection containing array that contain strings.</li>
872      * </ul>
873      *
874      * @param value     the value to "split"
875      * @param delimiter the delimiter for String values
876      * @return an iterator for accessing the single values
877      */
878     public static Iterator toIterator(Object value, char delimiter)
879     {
880         if (value == null)
881         {
882             return IteratorUtils.emptyIterator();
883         }
884         if (value instanceof String)
885         {
886             String s = (String) value;
887             if (s.indexOf(delimiter) > 0)
888             {
889                 return split((String) value, delimiter).iterator();
890             }
891             else
892             {
893                 return new SingletonIterator(value);
894             }
895         }
896         else if (value instanceof Collection)
897         {
898             return toIterator(((Collection) value).iterator(), delimiter);
899         }
900         else if (value.getClass().isArray())
901         {
902             return toIterator(IteratorUtils.arrayIterator(value), delimiter);
903         }
904         else if (value instanceof Iterator)
905         {
906             Iterator iterator = (Iterator) value;
907             IteratorChain chain = new IteratorChain();
908             while (iterator.hasNext())
909             {
910                 chain.addIterator(toIterator(iterator.next(), delimiter));
911             }
912             return chain;
913         }
914         else
915         {
916             return new SingletonIterator(value);
917         }
918     }
919 
920     /***
921      * Performs interpolation of the specified value. This method checks if the
922      * given value contains variables of the form <code>${...}</code>. If
923      * this is the case, all occurrances will be substituted by their current
924      * values.
925      *
926      * @param value the value to be interpolated
927      * @param config the current configuration object
928      * @return the interpolated value
929      */
930     public static Object interpolate(Object value, AbstractConfiguration config)
931     {
932         if (value instanceof String)
933         {
934             return config.getSubstitutor().replace((String) value);
935         }
936         else
937         {
938             return value;
939         }
940     }
941 }