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 }