001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.awt.Color;
021 import java.lang.reflect.Array;
022 import java.lang.reflect.Constructor;
023 import java.lang.reflect.InvocationTargetException;
024 import java.math.BigDecimal;
025 import java.math.BigInteger;
026 import java.net.InetAddress;
027 import java.net.MalformedURLException;
028 import java.net.URL;
029 import java.net.UnknownHostException;
030 import java.text.ParseException;
031 import java.text.SimpleDateFormat;
032 import java.util.ArrayList;
033 import java.util.Calendar;
034 import java.util.Collection;
035 import java.util.Date;
036 import java.util.Iterator;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.Locale;
040
041 import org.apache.commons.lang.BooleanUtils;
042 import org.apache.commons.lang.StringUtils;
043
044 /**
045 * A utility class to convert the configuration properties into any type.
046 *
047 * @author Emmanuel Bourg
048 * @version $Id: PropertyConverter.java 1301990 2012-03-17 20:10:46Z oheger $
049 * @since 1.1
050 */
051 public final class PropertyConverter
052 {
053 /** Constant for the list delimiter as char.*/
054 static final char LIST_ESC_CHAR = '\\';
055
056 /** Constant for the list delimiter escaping character as string.*/
057 static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR);
058
059 /** Constant for the prefix of hex numbers.*/
060 private static final String HEX_PREFIX = "0x";
061
062 /** Constant for the radix of hex numbers.*/
063 private static final int HEX_RADIX = 16;
064
065 /** Constant for the prefix of binary numbers.*/
066 private static final String BIN_PREFIX = "0b";
067
068 /** Constant for the radix of binary numbers.*/
069 private static final int BIN_RADIX = 2;
070
071 /** Constant for the argument classes of the Number constructor that takes a String. */
072 private static final Class<?>[] CONSTR_ARGS = {String.class};
073
074 /** The fully qualified name of {@link javax.mail.internet.InternetAddress} */
075 private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress";
076
077 /**
078 * Private constructor prevents instances from being created.
079 */
080 private PropertyConverter()
081 {
082 // to prevent instantiation...
083 }
084
085 /**
086 * Converts the specified value to the target class. If the class is a
087 * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned
088 * will use the wrapper type (Integer.class, Boolean.class, etc).
089 *
090 * @param cls the target class of the converted value
091 * @param value the value to convert
092 * @param params optional parameters used for the conversion
093 * @return the converted value
094 * @throws ConversionException if the value is not compatible with the requested type
095 *
096 * @since 1.5
097 */
098 static Object to(Class<?> cls, Object value, Object[] params) throws ConversionException
099 {
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 "flatten").</li>
935 * </ul>
936 *
937 * @param value the value to be processed
938 * @param delimiter the delimiter for String values
939 * @return a "flat" 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 }