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